B题:https://ac.nowcoder.com/acm/contest/368/B
有一棵n个节点的二叉树,1为根节点,每个节点有一个值wi。现在要选出尽量多的点。
对于任意一棵子树,都要满足:
如果选了根节点的话,在这棵子树内选的其他的点都要比根节点的值大;
如果在左子树选了一个点,在右子树中选的其他点要比它小。
题解:dfs序+LIS。根据题意可知首先dfs按照根,右,左的顺序求出该树的序列,再按照点的权值,求出该序列的最长上升子序列。
#include
using namespace std;
typedef long long ll;
int q[100010];
int w[100010];
int l[100010],r[100010];
int n;
int tot;
int dp[100010];
void dfs(int x)
{
if(!x) return ;
q[++tot]=x;
dfs(r[x]);
dfs(l[x]);
}
int search(int num,int low ,int high){
int mid;
while(low<=high){
mid=(low+high)>>1;
if(num>=dp[mid]) low =mid+1;
else high=mid-1;
}
return low;
}
int DP(int k)
{
int i,len,pos;
dp[1]=q[1];
len=1;
for(i=2;i<=k;i++){
if(q[i]>=dp[len]){
len+=1;
dp[len]=q[i];
}
else {
pos=search(q[i],1,len);
dp[pos]=q[i];
}
}
return len;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
}
for(int i=1;i<=n;i++){
scanf("%d %d",&l[i],&r[i]);
}
dfs(1);
for(int i=1;i<=n;i++) q[i]=w[q[i]];
int ans=0;
ans=DP(n);
printf("%d\n",ans);
return 0;
}
C题:https://ac.nowcoder.com/acm/contest/368/C
现在一共有n天,第i天如果有流星雨的话,会有颗流星雨。
第i天有流星雨的概率是。
如果第一天有流星雨了,那么第二天有流星雨的可能性是,否则是。相应的,如果第天有流星雨,第i天有流星雨的可能性是,否则是。
求n天后,流星雨颗数的期望。
题解:概率dp,设dp[i]为第i天下流星雨的概率,由题意可知,dp[i]=dp[i-1]*(P+p[i])+(1-dp[i-1])*p[i]。
#include
using namespace std;
typedef long long ll;
typedef long long LL;
#define mod 1000000007
LL binarypow(LL a,LL b,LL m) {
LL ans=1;
while(b>0){
if(b&1){
ans=ans*a%m;
}
a=a*a%m;
b>>=1;
}
return ans;
}
int n;
ll a,b;
ll x[100010],y[100010],w[100010];
ll dp[100010];
ll P,ans=0;
ll p[100010];
int main()
{
scanf("%d %lld %lld",&n,&a,&b);
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
}
for(int i=1;i<=n;i++){
scanf("%lld %lld",&x[i],&y[i]);
p[i]=x[i]*binarypow(y[i],mod-2,mod)%mod;
}
P=a*binarypow(b,mod-2,mod)%mod;
dp[1]=p[1];
ans=p[1]*w[1]%mod;
for(int i=2;i<=n;i++){
dp[i]=0;
dp[i]+=dp[i-1]*(P+p[i])%mod+(mod+1-dp[i-1])*p[i]%mod;
dp[i]%=mod;
ans+=(dp[i]*w[i]%mod);
ans%=mod;
}
printf("%lld\n",ans);
return 0;
}
D题:https://ac.nowcoder.com/acm/contest/368/D
小T有n个点,每个点可能是黑色的,可能是白色的。
小T对这张图的定义了白连通块和黑连通块:
白连通块:图中一个点集V,若满足所有点都是白点,并且V中任意两点都可以只经过V中的点互相到达,则称V中的点构成了一个白连通块。
黑连通块:类似白连通块的定义。
小T对这n个点m次操作。
1、在两个点之间连一条边。
2、询问白(黑)连通块个数。
3、给出x,y两个点,保证同色(为了方便描述,x,y都是白点,黑色同理)。询问存在多少个黑点,将它改变颜色后,x,y所在的白连通块会合并为一个。如果x,y已经在一个白连通块内了,输出-1。(注意:这里不会对点的颜色改变,只统计个数)
题解:bitset+并查集。对于询问2,可以在每次加边的过程中判断该次加边能否使连通块减少,询问3,可以用bitset来维护该两点所属的连通块所连了哪些异色点,通过与操作即可计算出这两个集合里有多少相同的1。
#include
using namespace std;
#define maxn 50010
bitset f[maxn],res;
int n,m;
int a[maxn],fa[maxn];
int cnt1,cnt2;
int findparent(int a){
int r=a;
while(r!=fa[r]){
r=fa[r];
}
while(a!=fa[a]){
int j=fa[a];
fa[a]=r;
a=j;
}
return r;
}
void addedge(int x,int y)
{
int fx=findparent(x);
int fy=findparent(y);
if(a[x]==a[y]){
if(fx!=fy){
fa[fx]=fy;
f[fy]|=f[fx];//注意合并这两个集合,因为它们要变成同一个连通块了
if(a[x]==1){
cnt1--;
}
else cnt2--;
}
}
else{
f[fx].set(y);//不是同色点,则用bitset来记录他们各自连接了哪个位置的异色点
f[fy].set(x);
}
}
void query1(int x)
{
if(x==0){
printf("%d\n",cnt2);
}
else {
printf("%d\n",cnt1);
}
}
void query2(int x,int y)
{
int fx=findparent(x);
int fy=findparent(y);
if(fx==fy){
printf("-1\n");
}
else{
res=f[fx]&f[fy];
printf("%d\n",res.count());
}
}
int main()
{
scanf("%d %d",&n,&m);
cnt1=0,cnt2=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]){
cnt1++;
}
else cnt2++;
}
int opt,x,y;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
scanf("%d",&opt);
if(opt==1){
scanf("%d %d",&x,&y);
addedge(x,y);
}
else if(opt==2){
scanf("%d",&x);
query1(x);
}
else if(opt==3){
scanf("%d %d",&x,&y);
query2(x,y);
}
}
return 0;
}
F题:https://ac.nowcoder.com/acm/contest/368/F
有一棵n个节点的异或树,1号点为根,每个节点有一个权值。每次询问给出u,x,询问子树u内,点的权值大于x的所有权值异或x的和。即
由于这是一棵异或树,所以,如果一个数出现了两次,那么这两个点的权值就消失了(点并没有消失,即树的形态没有发生变化,只是在计算时忽略这两个点的权值。)消失过程发生在一次询问时,如果子树内两个点的权值一样,那么这两个点的权值同时消失,直到无法再有点对消失后查询。
题解:权值线段树合并。官方题解:
首先按位处理,那么问题转化为求一个子树内,权值大于x的,1(或0)的个数。
可以对每棵子树建立一棵权值线段树,每个叶子节点维护两个值size和cnt[],表示这个权值出现的次数,以及这个权值二进制分解后,每一位上1的个数。权值线段树每个节点维护cnt[],表示子树内二进制下每一位上1的个数和,现在可以在O(log2V)
#include
#include
#include
#include
#include
#include
using namespace std;
#define M 120010
typedef long long ll;
int cnt;
int lp[M * 33], rp[M * 33], sz[M * 33], n, q, w[M], rot[M];//rot[i]:i号结点在权值线段树的根的编号,sz[i]:以根结点为i的子树内权值出现的次数,lp[i[,rp[i]:结点i的左右孩子的结点编号
vector to[M];
vector > que[M];
ll ans[M], poww[111];
struct Node{
int sum[17];
}node[M*33];
void pushup(int now)
{
sz[now]=sz[lp[now]]+sz[rp[now]];
for(int i=0;i<=16;i++) node[now].sum[i]=node[lp[now]].sum[i]+node[rp[now]].sum[i];
}
void insert(int l,int r,int &now,int val)//权值线段树叶子节点的插入操作
{
if(now==0) now=++cnt;//分配一个新的结点编号
if(l==r){//找到该权值的位置,进行插入操作(插入要维护的值)
if(sz[now]==1){
sz[now]=0;
for(int i=0;i<=16;i++){
node[now].sum[i]=0;
}
}
else {
sz[now]=1;
for(int i=0;i<=16;i++){
if(val&poww[i]) node[now].sum[i]=1;
else node[now].sum[i]=0;
}
}
return ;
}
int mid=(l+r)>>1;
if(val<=mid) insert(l,mid,lp[now],val);
else insert(mid+1,r,rp[now],val);
pushup(now);
}
int merge(int l,int r,int last,int now)//合并操作
{
if(!now||!last) return now+last;
if(l==r){//叶子结点的合并
sz[last]=(sz[now]^sz[last]);
for(int i=0;i<=16;i++){
node[last].sum[i]=(node[now].sum[i]+node[last].sum[i])&1;
}
return last;
}
int mid=(l+r)>>1;
lp[last]=merge(l,mid,lp[last],lp[now]);//合并两颗树的左孩子
rp[last]=merge(mid+1,r,rp[last],rp[now]);//合并右孩子
pushup(last);
return last;
}
ll query(int l,int r,int now,int x)
{
if(sz[now]==0||now==0) return 0;
if(r<=x) return 0;
if(l>x){
ll res=0;
for(int i=0;i<=16;i++){
int a=node[now].sum[i];int b=sz[now]-a;
if(poww[i]&x) res+=poww[i]*b;
else res+=poww[i]*a;
}
return res;
}
int mid=(l+r)>>1;
return query(l,mid,lp[now],x)+query(mid+1,r,rp[now],x);
}
void dfs(int now,int fa)//以dfs序来合并每颗线段树
{
for(int i=0;i<(int)to[now].size();i++){
if(to[now][i]==fa) continue;
dfs(to[now][i],now);
rot[now]=merge(1,n,rot[now],rot[to[now][i]]);
}
for(int i=0;i<(int)que[now].size();i++){//每合并完一个就对该树进行查询
ans[que[now][i].second]=query(1,n,rot[now],que[now][i].first);
}
}
int main()
{
scanf("%d %d",&n,&q);
poww[0]=1;
for(int i=1;i<=16;i++) poww[i]=poww[i-1]<<1;
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i