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\n",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\n");
            }
            else if(cnt%2==0){
                printf("BOB\n");
            }
            else{
                printf("ALICE\n");
            }
        }
        else{
            if(cnt==1){
                printf("ALICE\n");
            }
            else{
                if((cnt-sum)%2==0){
                    printf("ALICE\n");
                }
                else{
                    if(cnt==2){
                        printf("DRAW\n");
                    }
                    else{
                        printf("ALICE\n");
                    }
                }
            }
        }
    }
}

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\n",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\n",f[n]);
}
posted @ 2021-05-21 00:32  ccsu_madoka  阅读(130)  评论(0编辑  收藏  举报