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]);
}