2020ICPC昆明题解

最近VP了2021年的昆明区域赛,补几道题并总结一下教训

前言

在这里插入图片描述
本次总共写出来四题,其中 H H H题大水题一个, I I I题是计算几何二维平面中的板子题而且以前写过,所以在还算比较快的时间开出来了, J J J题强行当了一把 d d l ddl ddl战神,样例都没测就交了, L L L题连结论都没有好好推出来,就让 s g l y sgly sgly上机写了个树状数组,稀里糊涂的过了


A.AC

反悔贪心
c [ i ] c[i] c[i]表示将字符串第 i i i位和第 i + 1 i+1 i+1位分别变为 a a a c c c所需要的操作数,相邻的 c [ i ] c[i] c[i]不能重复拿取,于是问题就转换成了种树
时间复杂度 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=5e5+7;
int n,k,c[maxn],pre[maxn],nex[maxn],vis[maxn],ans=0,num=0,flag[maxn];
char s[maxn];
void del(int x)
{
	vis[x]=1;
	pre[nex[x]]=pre[x];
	nex[pre[x]]=nex[x];
}
int main()
{
	priority_queue<PII,vector<PII>,greater<PII>>q;
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	for(int i=1;i<n;i++)
	{
		if(s[i]=='a' && s[i+1]=='c') c[i]=0;
		else if(s[i]=='a' || s[i+1]=='c') c[i]=1;
		else c[i]=2;
		q.push({c[i],i});
		pre[i]=i-1;nex[i]=i+1;
	}
	c[0]=inf;c[n]=inf;
	while(!q.empty())
	{
		if(ans==n/2) break;
		while(vis[q.top().se]) q.pop();
		if(num+q.top().fi>k) break;
		int u=q.top().se;
		q.pop();
		num+=c[u];
		flag[u]++;
		c[u]=c[pre[u]]+c[nex[u]]-c[u];
		q.push({c[u],u});
		del(pre[u]);del(nex[u]);
		ans++;
	}
	for(int i=1;i<n;i++)
	{
		if(!flag[i] ) continue;
		for(int j=i-flag[i]+1;j<i+1+flag[i];j+=2)
		{
			s[j]='a';s[j+1]='c';
		}
		
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) printf("%c",s[i]);
}

C. Cities

对于一个长度为 m m m且没有相同元素的区间,需要操作 m − 1 m-1 m1次才可维护
对于一段颜色相同的区间,其意义完全等同于一个独立的点,于是贪心的将相同区间缩为一个点
考虑区间 d p dp dp来维护答案, d p [ l ] [ r ] dp[l][r] dp[l][r]表示将 [ l , r ] [l,r] [l,r]颜色全部变为 c [ r ] c[r] c[r]的最小操作次数
所以若区间 [ l , r ] [l,r] [l,r]没有相同的元素, d p [ l ] [ r ] = d p [ l ] [ r − 1 ] + 1 dp[l][r]=dp[l][r-1]+1 dp[l][r]=dp[l][r1]+1
r r r k k k颜色相同,则有 d p [ l ] [ r ] = d p [ l ] [ k ] + d p [ k + 1 ] [ r ] dp[l][r]=dp[l][k]+dp[k+1][r] dp[l][r]=dp[l][k]+dp[k+1][r]
由于同一个数字出现的次数不超过 15 15 15次,所以时间复杂度 O ( n 2 ∗ 15 ) O(n^2*15) O(n215)

#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=2e5+7;
int n,a[5005],f[5005][5005],vis[5005],nex[5005];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			vis[i]=0;nex[i]=0;
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			if(a[i]==a[i-1])
			{
				i--;n--;
			}	
		}
		for(int i=1;i<=n;i++)
		{
			f[i][i]=0;
			nex[i]=vis[a[i]];
			vis[a[i]]=i;
		}
		for(int len=2;len<=n;len++)
		{
			for(int l=1;l+len-1<=n;l++)
			{
				int r=l+len-1;
				f[l][r]=f[l][r-1]+1;
				for(int k=nex[r];k>=l;k=nex[k])
				{
					f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
				}
				
			}
		}
		printf("%d\n",f[1][n]);
	}
}

G.Gift

对于每个朋友,我们有三种选择,给他做蛋糕,给他送礼物,什么都不送
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示对于前 i i i个朋友,为其中的 j j j个人准备礼物,在今年的第 k k k天时可以收获的最大价值
给朋友 i i i做蛋糕 f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j ] [ k − c ] + v ) f[i][j][k]=max(f[i][j][k],f[i-1][j][k-c]+v) f[i][j][k]=max(f[i][j][k],f[i1][j][kc]+v)
给朋友 i i i送礼物 f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j − 1 ] [ k ] ) f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k]) f[i][j][k]=max(f[i][j][k],f[i1][j1][k])
什么都不送 f [ i ] [ j ] [ k ] = m a x ( f [ i − 1 ] [ j ] [ k ] , f [ i ] [ j ] [ k ] ) f[i][j][k]=max(f[i-1][j][k],f[i][j][k]) f[i][j][k]=max(f[i1][j][k],f[i][j][k])
对于送礼物得到的价值,只需要二进制暴力枚举即可
时间复杂度 O ( n ∗ m ∗ 265 + 2 m ) O(n*m*265+2^{m}) O(nm265+2m)

#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=2e5+7;
int n,m,w;
int f[2][20][400];
int year,month,day,c,v;
int d[12]={0,31,59,90,120,151,181,212,243,273,304,334};
int a[25],b[25],g[20];
struct node
{
	int day,c,v;
}peop[505];
bool cmp(node a,node b)
{
	return a.day<b.day;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(f,-0x3f,sizeof(f));
        memset(g,0,sizeof(g));
		scanf("%d%d%d",&n,&m,&w);
		for(int i=1;i<=n;i++)
		{
			scanf("%d-%d-%d",&year,&month,&day);
			scanf("%d%d",&c,&v);
			if(month==2 && day==29)
			{
				i--;n--;continue;
			}
			peop[i]={d[month-1]+day,c,v};
		}
		for(int i=1;i<=m;i++) 
		scanf("%d%d",&a[i],&b[i]);
		for(int i=0;i<(1<<m);i++)
		{
			int u=0,val=0,pri=0;
			for(int j=1;j<=m;j++)
			{
				if(i>>(j-1)&1) u++,val+=b[j],pri+=a[j];
			}
			if(pri>w) continue;
			g[u]=max(g[u],val);
		}
		sort(peop+1,peop+n+1,cmp);
        f[0][0][0]=0;
		for(int i=1;i<=n;i++)
		{
			int dd=peop[i].day;
			for(int j=0;j<=m&&j<=i;j++)
			{
				for(int k=0;k<=365;k++)
				{
					int u=i%2;
					f[u][j][k]=max(f[u^1][j][k],f[u][j][k]);
					if(j>0)
					f[u][j][k]=max(f[u][j][k],f[u^1][j-1][k]);
					if(k>=peop[i].c && k<=dd)
					{
						f[u][j][k]=max(f[u][j][k],f[u^1][j][k-peop[i].c]+peop[i].v);
					}
				}
			}
		}
		int ans=0;
		for(int j=0;j<=m;j++)
		{
			for(int k=0;k<=365;k++)
			{
				ans=max(ans,f[n%2][j][k]+g[j]);
			}
		}
		printf("%d\n",ans);
	}
}

J.Parallel Sort

由于是排列,所以肯定是一些数字的环组成的,对于一个环,要环上的点统一移动一步就可以归位
这只需要最多两次操作就可以实现
假设要把 ( 1 , 2... , n ) (1,2...,n) (1,2...,n)变成 ( 2 , 3 , . . . , n , 1 ) (2,3,...,n,1) (2,3,...,n,1),只需要先变成 ( 1 , n , n − 1 , . . . , 2 ) (1,n,n-1,...,2) (1,n,n1,...,2),再变成 ( 2 , 3 , . . . , n , 1 ) (2,3,...,n,1) (2,3,...,n,1)即可

赛时这题浪费挺长时间的,在数字和数字下标里迷得云里雾里,赛后看题解没想到这么简单

L.Simone and graph coloring

该题最大颜色数等价于求一个最长的下降子序列
所以用树状数组或者二分去写即可

#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=1e6+7;
int a[maxn],col[maxn],ans,n,tre[maxn];
void update(int x,int num)
{
	for(;x<=n;x+=lowbit(x)) tre[x]=max(tre[x],num);
}
int query(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res=max(res,tre[x]);
	return res;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		ans=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=n;i++) tre[i]=0;
		for(int i=n;i>=1;i--)
		{
			col[i]=query(a[i]-1)+1;
			update(a[i],col[i]);
			ans=max(ans,col[i]);
		}
		printf("%d\n",ans);
		for(int i=1;i<=n;i++) printf("%d ",col[i]);
		puts("");
	}
}

M.Stone Games

对于区间 [ L , R ] [L,R] [L,R]无法相加构造出来的最小整数 s u m sum sum,建立权值线段树,假设当前可以构造出来的一个整数为 s u m sum sum,通过查询小于 s u m + 1 sum+1 sum+1的数字的数字和,则可以不断维护新的 s u m sum sum不断更新答案,对于多个 [ L , R ] [L,R] [L,R]区间上的查询,只需要主席树即可
时间复杂度 O ( q ∗ l o g ( n ) 2 ) O(q*log(n)^2) O(qlog(n)2)

#include<bits/stdc++.h>
#define fi first
#define gcd __gcd
#define se second
#define int long long
#define pb push_back
#define lowbit(x) x&-x
#define inf 0x3f3f3f3f
#define mem(x,y) memset(x,y,sizeof(x))
#define lrb666 ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
typedef pair<int,int> PII;
const int maxn=1e6+7;
int n,q,cnt;
int a[3*maxn],root[3*maxn];
vector<int>v;
int tr[maxn*4*20],lch[maxn*4*20],rch[maxn*4*20],sum[maxn*4*20];
int build(int l,int r)
{
	int pos=++cnt;
	lch[pos]=0,rch[pos]=0,sum[pos]=0;
	if(l<r)
	{
		int mid=l+r>>1;
		lch[pos]=build(l,mid);
		rch[pos]=build(mid+1,r);
	}
	return pos;
}
int update(int pre,int l,int r,int x,int v)
{
	int pos=++cnt;
	lch[pos]=lch[pre],rch[pos]=rch[pre],sum[pos]=sum[pre]+v;
	if(l<r)
	{
		int mid=l+r>>1;
		if(x<=mid) lch[pos]=update(lch[pre],l,mid,x,v);
		else rch[pos]=update(rch[pre],mid+1,r,x,v);
	}
	return pos;
}
int query(int x,int y,int l,int r)
{
	if(l==r) return sum[y];
	int mid=l+r>>1;
	if(x<=mid) return query(x,lch[y],l,mid);
	else return query(x,rch[y],mid+1,r)+sum[lch[y]];
}
int find(int num)
{
	return lower_bound(v.begin(),v.end(),num)-v.begin();
}
signed main()
{
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i]);
		v.pb(a[i]);v.pb(a[i]+1);v.pb(a[i]-1);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	int sz=v.size();
	for(int i=1;i<=n;i++)
	{
		int x=find(a[i]);
		root[i]=update(root[i-1],0,sz-1,x,a[i]);
	}
	int ans=0;
	while(q--)
	{
		int l1,r1,l,r;
		scanf("%lld%lld",&l1,&r1);
		l=min((l1+ans)%n+1,(r1+ans)%n+1);
		r=max((l1+ans)%n+1,(r1+ans)%n+1);
		int num=0;
		while(1)
		{
			int x=find(num+1);
			int cal=query(x,root[r],0,sz-1)-query(x,root[l-1],0,sz-1);
			if(cal<=num) 
			{
				ans=num+1; break;
			}
			num=cal;
		}
		printf("%lld\n",ans);
	}
}


希望周日的昆明加油

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值