zoukankan      html  css  js  c++  java
  • Codeforces Round #721 (Div. 2)(B-E)

    Codeforces Round #721 (Div. 2)(B-E)

    Codeforces Round #721 (Div. 2)

    C. Sequence Pair Weight

    题意:给一个数组,求他的所有连续子串中,任取相等的两数的方案之和。

    题解:这个题其实,造个全是1的数组乱搞算出来就差不多了。

    当计算i的贡献时,我们计算前面所有a[i]的贡献,同时对于每一个包含i的后缀都可以算一次i前面的贡献,所以ans加上map[a[i]]*(n-i+1);

    当a[i]在第i个位置时,可为后面的数贡献i个子串,所以每次算完贡献后我们在map[a[i]]中加i

    #include<iostream>
    #include<map>
    using namespace std;
    #define ll long long
    const ll N=1e5+7;
    ll t,n,a[N];
    map<ll,ll>ma;
    int main(){
        scanf("%lld",&t);
        while(t--){
            scanf("%lld",&n);
            for(int i=1;i<=n;i++){
                scanf("%lld",&a[i]);
            }
            ma.clear();
            long long ans=0;
            for(int i=1;i<=n;i++){
                ll res=ma[a[i]];
                ans+=res*(n-i+1);
                ma[a[i]]+=i;
            }
            printf("%lld
    ",ans);
        }
    }
    

    B2. Palindrome Game (hard version)

    题意:B1的升级版,给一个01字符串,每次可以两种操作

    1,将一个0变成1,花费1;

    2,将字符串反转,花费0(要求字符串非回文,且上次操作未反转)

    A与B轮流操作,A先手,全1结束,博弈看谁最后花钱最少。

    题解:

    分情况讨论清楚即可,由简易版我们知道,我们可以与对面下对称位,在最后还有两个0时反转,这样对方就会多花2费。

    难版增加了字符串不一定回文的条件,那么其实我只要一开始反转,对方就只能一直填1直到回文,所以对方一定想尽快使字符串回文,那么对方的走法也基本上被我控制了。

    对于未回文时,我们一直反转等待最后一步造成回文得到翻转控制权,便就和第一题解法一样了。

    同时谈论一下,中间点的值,与0数等于1时,等于2时的特殊样例即可。

    #include<iostream>
    using namespace std;
    const int N=1e3+7;
    int t,n;
    char s[N];
    int main(){
        scanf("%d",&t);
        while(t--){
            scanf("%d",&n);
            scanf("%s",s+1);
            int cnt=0;
            int sum=0;
            for(int i=1;i<=n;i++){
                if(s[i]=='0')cnt++;
                if(s[i]!=s[n-i+1])sum++;
            }
            sum/=2;
            if(sum==0){
                if(cnt==1){
                    printf("BOB
    ");
                }
                else if(cnt%2==0){
                    printf("BOB
    ");
                }
                else{
                    printf("ALICE
    ");
                }
            }
            else{
                if(cnt==1){
                    printf("ALICE
    ");
                }
                else{
                    if((cnt-sum)%2==0){
                        printf("ALICE
    ");
                    }
                    else{
                        if(cnt==2){
                            printf("DRAW
    ");
                        }
                        else{
                            printf("ALICE
    ");
                        }
                    }
                }
            }
        }
    }
    

    D. MEX Tree

    题意:给一颗树,点权为点的标号,问有多少条路径的mex等于k,输出k从0至n的答案。

    题解:

    思考如何一条路径才能使mex等于k,一条路径里0到k-1的点都要有,恰好无k的路径能满足。

    那么我们可以发现一些性质:

    1,0至k-1必须没有分支,用可以一条线就可以划过这k个点。

    2,划过0至k-1的一条最短的线中不能包含k。

    然后我们开始做题:

    先dfs预处理部分信息,然后分3步解决,计算ans[0],ans[1],ans[2~k],因为他们计算方法不同。

    ans[0]即不能有0,即所取链不能通过0,那么答案就是其每个子树中任取两点之和。

    ans[1]要通过0而不能通过1,我们可以直接无视1子树,相当于一颗树有多少链通过根节点,树形dp的经典求法。

    ans[2~k]就比较麻烦了,主要要利用上面两个性质,设当前最短链的头为x,尾为y,我们可以通过求x与i,y与i的lca来求得i对于链的位置,这部分大家就自己思考一下吧,不懂看我代码就行。

    #include<iostream>
    #include<vector>
    using namespace std;
    #define ll long long
    const ll N=2e5+7;
    ll t,n;
    ll f[N][32],er[32],d[N],sz[N],ans[N];
    vector<ll>ho[N];
    void dfs(ll p,ll fa){
    	f[p][0]=fa;
    	d[p]=d[fa]+1;
    	sz[p]=1;
    	for(int i=0;i<ho[p].size();i++){
    		int to=ho[p][i];
    		if(to==fa)continue;
    		dfs(to,p);
    		sz[p]+=sz[to];
    	}
    }
    void init(){
    	er[0]=1;
    	for(int i=1;i<=20;i++){
    		er[i]=er[i-1]*2;
    		for(int j=1;j<=n;j++){
    			f[j][i]=f[f[j][i-1]][i-1];
    		}
    	}
    }
    ll lca(ll u,ll v){
    	if(d[u]<d[v])swap(u,v);
    	if(d[u]!=d[v]){
    		for(int i=20;i>=0;i--){
    			if(d[u]-er[i]>=d[v])u=f[u][i];
    		}
    	}
    	if(u==v)return u;
    	else{
    		for(int i=20;i>=0;i--){
    			if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
    		}
    	}
    	return f[u][0];
    }
    ll C(ll z){
    	return z*(z-1)/2;
    }
    int main(){
    	scanf("%lld",&t);
    	while(t--){
    		scanf("%lld",&n);
    		for(int i=0;i<=n;i++){
    			ho[i].clear();
    			ans[i]=0;
    			d[i]=0;
    			sz[i]=0;
    		}
    		for(int i=1;i<n;i++){
    			ll u,v;
    			scanf("%lld%lld",&u,&v);
    			ho[u].push_back(v);
    			ho[v].push_back(u);
    		}
    		dfs(0,0);
    		init();
    		for(int i=0;i<ho[0].size();i++){
    			ll to=ho[0][i];
    			ans[0]+=C(sz[to]);
    		}
    		ll f1=0,f2=0,cnt=0;
    		for(int i=1;i<=n;i++){
    			if(cnt==0){
    				ll sum=0;
    				ans[1]=sz[0]-sz[1]-1;
    				for(int j=0;j<ho[0].size();j++){
    					ll to=ho[0][j];
    					ll z=sz[to];
    					ll p=lca(to,1);
    					if(p==to){
    						sz[0]-=z;
    						z-=sz[1];
    					}
    					ans[1]+=sum*z;
    					sum+=z;
    				}
    				f1=1;
    				cnt++;
    			}
    			else if(cnt==1){
    				ll p=lca(f1,i);
    				if(p==i){
    					continue;
    				}
    				else if(p==0){
    					ans[i]=(sz[0]-sz[i])*sz[f1];
    					f2=i;
    					cnt++;
    				}
    				else if(p==f1){
    					ans[i]=sz[0]*(sz[f1]-sz[i]);
    					f1=i;
    				}
    				else{
    					ans[i]=sz[0]*sz[f1];
    					break;
    				}
    			}
    			else{
    				ll p1=lca(f1,i);
    				ll p2=lca(f2,i);
    				if(p1==i||p2==i){
    					continue;
    				}
    				else if(p1==0&&p2==0){
    					ans[i]=sz[f1]*sz[f2];
    					break;
    				}
    				else if(p1==f1){
    					ans[i]=(sz[f1]-sz[i])*sz[f2];
    					f1=i;
    				}
    				else if(p2==f2){
    					ans[i]=sz[f1]*(sz[f2]-sz[i]);
    					f2=i;
    				}
    				else{
    					ans[i]=sz[f1]*sz[f2];
    					break;
    				}
    			}
    		}
    		for(int i=0;i<=n;i++){
    			printf("%lld ",ans[i]);
    		}puts("");
    	}
    }
    

    E. Partition Game

    题意:给一个序列,要求你分成k段,使每段贡献值之和最小,一段的贡献等于,在一段中的所有种类数最右端减去最左端的值之和。

    题解:这题一上手,感觉没什么思路,我们不妨先写个无优化的dp

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define ll long long
    const ll N=1e5+7;
    ll n,k,a[N];
    ll f[107][N];
    ll mi[N],mx[N];
    ll cal(ll l,ll r){
    	memset(mi,0,sizeof(mi));
    	memset(mx,0,sizeof(mx));
    	ll sum=0;
    	for(int i=l;i<=r;i++){
    		if(!mi[a[i]])mi[a[i]]=i;
    		mx[a[i]]=i;
    	}
    	for(int i=1;i<=n;i++){
    		sum+=mx[i]-mi[i];
    	}
    	return sum;
    }
    int main(){
    	scanf("%lld%lld",&n,&k);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    	}
    	memset(f,0x3f,sizeof(f));
    	f[0][0]=0;
    	for(int i=1;i<=k;i++){
    		for(int j=1;j<=n;j++){
    			for(int p=0;p<j;p++){
    				f[i][j]=min(f[i][j],f[i-1][p]+cal(p+1,j));
    			}
    		}
    	}
    	printf("%lld
    ",f[k][n]);
    }
    

    设将前j位分成i段的花费是$f[i][j]$

    而它的值由取决于$f[i-1][p]+cal(p+1,j)$,我们只需要得到最小值就行了,f数组是上一层的答案,是定值可先无视,而重要的是如何快速求解cal(cal表示一段中的贡献)。

    假设我们已知cal(j-1)数组的所有值(上一层已算),我们通过增加一个a[j]会使cal(j-1)数组如何变化呢,很明显从1至最近一个值为a[j]索引x的所有值都会增加一个j-x。(这里可以自己思考一下)。

    那么问题就很简单了,涉及到区间最小值,区间修改,单点赋值,我们用线段树优化即可。

    #include<iostream>
    #include<cstring>
    using namespace std;
    #define ll long long
    const ll N=1e5+7;
    const ll inf=1e18;
    ll n,k,a[N],to[N],num[N],f[N];
    struct madoka{
    	ll l;
    	ll r;
    	ll z;
    	ll f;
    }ma[4*N];
    void build(ll l,ll r,ll k){
    	ma[k].l=l;
    	ma[k].r=r;
    	if(l==r){
    		return;
    	}
    	ll mid=(l+r)/2;
    	build(l,mid,k*2);
    	build(mid+1,r,k*2+1);
    }
    void down(ll k){
    	if(ma[k].f>0&&ma[k].l!=ma[k].r){
    		ma[k*2].f+=ma[k].f;
    		ma[k*2].z+=ma[k].f;
    		ma[k*2+1].f+=ma[k].f;
    		ma[k*2+1].z+=ma[k].f;
    		ma[k].f=0;
    	}
    }
    void up(ll k,ll l,ll r,ll z){
    	down(k);
    	if(l<=ma[k].l&&ma[k].r<=r){
    		ma[k].z+=z;
    		ma[k].f+=z;
    		return;
    	}
    	ll mid=(ma[k].l+ma[k].r)/2;
    	if(l<=mid){
    		up(k*2,l,r,z);
    	}
    	if(mid<r){
    		up(k*2+1,l,r,z);
    	}
    	ma[k].z=min(ma[k*2].z,ma[k*2+1].z);
    }
    ll qry(ll k,ll l,ll r){
    	down(k);
    	if(l<=ma[k].l&&ma[k].r<=r){
    		return ma[k].z;
    	}
    	ll mid=(ma[k].l+ma[k].r)/2,mi=inf;
    	if(l<=mid){
    		mi=min(mi,qry(k*2,l,r));
    	}
    	if(mid<r){
    		mi=min(mi,qry(k*2+1,l,r));
    	}
    	return mi;
    }
    void init(ll l,ll r,ll k){
    	ma[k].f=0;
    	if(l==r){
    		ma[k].z=f[l-1];
    		return;
    	}
    	ll mid=(l+r)/2;
    	init(l,mid,k*2);
    	init(mid+1,r,k*2+1);
    	ma[k].z=min(ma[k*2].z,ma[k*2+1].z);
    }
    void print(ll l,ll r,ll k){
    	down(k);
    	if(l==r){
    		cout<<ma[k].z<<" ";
    		return;
    	}
    	ll mid=(l+r)/2;
    	print(l,mid,k*2);
    	print(mid+1,r,k*2+1);
    }
    int main(){
    	scanf("%lld%lld",&n,&k);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		to[i]=(num[a[i]]==0)?i:num[a[i]];
    		num[a[i]]=i;
    	}
    	build(1,n,1);
    	memset(f,0x3f,sizeof(f));
    	f[0]=0;
    	for(int i=1;i<=k;i++){
    		init(1,n,1);
    		//print(1,n,1);puts("");
    		for(int j=1;j<=n;j++){
    			up(1,1,to[j],j-to[j]);
    			//print(1,n,1);puts("");
    			f[j]=qry(1,1,j);
    		}
    	}
    	printf("%lld
    ",f[n]);
    }
    
  • 相关阅读:
    重学Mybatis从入门到源码之一
    Ribbon的负载均衡策略及使用方法
    SpringCloud之Ribbon的使用及源码解析
    FeignClient spi 调用 短路异常 & 线程池配置
    springboot 中yml配置
    jrebel 启动失败的处理
    使用@Cacheable注解时,Redis连不上,直接调用方法内部的解决方案
    redis scan 命令指南
    正式学习单元测试
    Cannot assign requested address 和 SO_REUSEADDR 参数
  • 原文地址:https://www.cnblogs.com/whitelily/p/14792137.html
Copyright © 2011-2022 走看看