【超好懂的比赛题解】2021ICPC台北站 个人题解

57 篇文章 0 订阅
12 篇文章 0 订阅

title : 2021ICPC台北站 个人题解
date : 2022-10-4
tags : ACM,题解,练习记录
author : Linno


2021ICPC台北站 个人题解

题目链接:https://codeforces.com/gym/103443

补题进度:8/12

A-Ice Cream

签到题,数据范围也很少,一个一个数就行了。

#include<bits/stdc++.h>
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T,x,y,n;
	cin>>T;
	while(T--){
		cin>>x>>y>>n;
		int now=0,cnt=0,cost=0;
		while(now<n){
			++now;++cnt;
			cost+=3;
			if(cnt==x){
				cnt=0;
				now+=y;
			}
		}
		cout<<cost<<"\n";
	}
	return 0;
}

B-Maximum Sub-Reverse Matching

区间dp,我们可以用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从区间 [ i , j ] [i,j] [i,j]翻转后的1的个数,初始状态为 d p [ i ] [ i + 1 ] = ( S [ i ] = = T [ i + 1 ] ) + ( S [ i + 1 ] = = T [ i ] ) dp[i][i+1]=(S[i]==T[i+1])+(S[i+1]==T[i]) dp[i][i+1]=(S[i]==T[i+1])+(S[i+1]==T[i]),转移方程为 d p [ l ] [ r ] = m a x ( d p [ l ] [ r ] , d p [ l + 1 ] [ r − 1 ] + [ 两边交换后相等 ] ) dp[l][r]=max(dp[l][r],dp[l+1][r-1]+[两边交换后相等]) dp[l][r]=max(dp[l][r],dp[l+1][r1]+[两边交换后相等])

#include<bits/stdc++.h>
using namespace std;
const int N=1007;
string S,T,R;

int t,n,sum[N];
int mp[N][N],dp[N][N];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n;
		cin>>S>>T;
		for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) mp[i][j]=dp[i][j]=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				if(S[i-1]==T[j-1]) mp[i][j]=1;
			}
		}
		int mx1=0,mx2=0,ansl=1,ansr=1;
		sum[0]=0;
		for(int i=1;i<=n;++i){
			dp[i][i]=(S[i-1]==T[i-1]);
			sum[i]=sum[i-1]+(S[i-1]==T[i-1]);
		}
		mx1=mx2=sum[n];
		for(int i=1;i<n;++i){
			dp[i][i+1]=mp[i+1][i]+mp[i][i+1];
			int tmp=sum[n]-(sum[i+1]-sum[i-1])+dp[i][i+1];
			if(tmp>mx2) mx2=tmp,ansl=i,ansr=i+1;
		}
		for(int len=3;len<=n;++len){
			for(int l=1;l+len-1<=n;++l){
				int r=l+len-1;
				dp[l][r]=max(dp[l][r],dp[l+1][r-1]+mp[l][r]+mp[r][l]);
				int tmp=sum[n]-(sum[r]-sum[l-1])+dp[l][r];
				if(tmp>mx2) mx2=tmp,ansl=l,ansr=r;
			}
		}
		cout<<mx1<<" "<<mx2<<" "<<ansl<<" "<<ansr<<"\n"; 
	}
}

C-Community Service

题意:每次添加一条线段,或者询问与该区间有交的线段中最后一条被放入的线段,并删除该线段。我们只需要在线段树上的每个结点挂vector,分别记录区间标记和区间中线段序列 v v v。每次我们询问的时候就是取走最高的那个 v . b a c k ( ) v.back() v.back()。复杂度是 O ( m l o g n ) O(mlogn) O(mlogn)的。

#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((tr[p].l+tr[p].r)>>1)
using namespace std;
const int N=1e6+7;

struct Seg{ 
	int l,r;
	vector<int>tg,vv;
}tr[N<<2];

bool vis[N];
char s[N][20];

void build(int p,int l,int r){ //初始化线段树 
	tr[p].l=l;tr[p].r=r;//tr[p].tg.clear();tr[p].vv.clear();
	if(l==r) return;
	build(ls,l,mid);
	build(rs,mid+1,r);
}

void update(int p,int ql,int qr,int val){
	tr[p].vv.emplace_back(val);
	if(ql<=tr[p].l&&tr[p].r<=qr){
		tr[p].tg.emplace_back(val);
		return;	
	}
	if(ql<=mid) update(ls,ql,qr,val);
	if(qr>mid) update(rs,ql,qr,val);
}

int query(int p,int ql,int qr){
	int res=0;
	while(tr[p].tg.size()>0&&vis[tr[p].tg.back()]) tr[p].tg.pop_back();
	if(tr[p].tg.size()) res=max(res,tr[p].tg.back());
	if(ql<=tr[p].l&&tr[p].r<=qr){
		while(tr[p].vv.size()>0&&vis[tr[p].vv.back()]) tr[p].vv.pop_back();
		if(tr[p].vv.size()) res=max(res,tr[p].vv.back());
		return res;
	}
	if(ql<=mid) res=max(res,query(ls,ql,qr));
	if(qr>mid) res=max(res,query(rs,ql,qr));
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int n,m,l,r,op;
	cin>>n>>m;
	build(1,1,n);
	for(int i=1;i<=m;++i){
		cin>>op;
		if(op==1){
			cin>>s[i]>>l>>r;
			++l;++r;
			update(1,l,r,i);
		}else{
			cin>>l>>r;
			++l;++r;
			int pos=query(1,l,r);
			if(!pos) cout<<">_<\n";
			else cout<<s[pos]<<"\n",vis[pos]=1;
			
		}
	}
	return 0;
}

F-What a Colorful Wall

倒着枚举矩形,很容易想到用线段树/扫描线做,离散化之后暴力枚举y坐标,然后再倒着枚举每个矩形,如果被扫到就看能不能在所在的x区间涂色,复杂度是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的,卡不过去(当时卡得很煎熬)。我们把线段树改成并查集,对于枚举的所有矩形,来说,因为有了路径压缩,复杂度就降到了 O ( k n 2 ) O(kn^2) O(kn2)了。

#include<bits/stdc++.h>
using namespace std;
const int N=8005;

int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
template<class type_name> inline type_name qr(type_name sample){
    type_name ret=0,sgn=1;
    char cur=getchar();
    while(!isdigit(cur)) sgn=(cur=='-'?-1:1),cur=getchar();
    while(isdigit(cur)) ret=(ret<<1)+(ret<<3)+cur-'0',cur=getchar();
    return sgn==-1?-ret:ret;
}

int n,ans=0;
bitset<N>cnt; 
vector<int>vx,vy;

struct Q{
	int x1,x2,y1,y2,c;
	inline void read(){
		x1=qr(1);y1=qr(1);x2=qr(1);y2=qr(1);c=qr(1);
		vx.emplace_back(x1);
		vx.emplace_back(x2);
		vy.emplace_back(y1);
		vy.emplace_back(y2);
	}
}q[N];

struct DSU{
	vector<int>fa,sz;
	DSU(int siz):fa(siz),sz(siz,1){iota(fa.begin(),fa.end(),0);}
	inline int find(int x){
		return (fa[x]==x)?x:(fa[x]=find(fa[x]));
	}
	bool merge(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx==fy) return 0;
		fa[fx]=fy;
		sz[fy]+=sz[fx];
		return 1;
	}
	int size(int x){return sz[find(x)];}
};

int find(vector<int>v,int x){
	return lower_bound(v.begin(),v.end(),x)-v.begin();
}

signed main(){
	n=qr(1);
	for(int i=1;i<=n;++i) q[i].read();
	sort(vx.begin(),vx.end());
	vx.erase(unique(vx.begin(),vx.end()),vx.end());
	sort(vy.begin(),vy.end());
	vy.erase(unique(vy.begin(),vy.end()),vy.end());
	int sx=vx.size(),sy=vy.size();
	for(int i=1;i<=n;++i){
		int x1=find(vx,q[i].x1),x2=find(vx,q[i].x2);
		int y1=find(vy,q[i].y1),y2=find(vy,q[i].y2);
		q[i]=(Q){x1,x2,y1,y2,q[i].c};
	}
	for(int y=0;y<sy;++y){  //先枚举y轴
		DSU dsu(sy*2+1);
		for(int i=n;i>=1;--i){
			if(q[i].y2<=y&&y<q[i].y1){ //如果在矩形范围内
				for(int k=dsu.find(q[i].x1);k<q[i].x2;k=dsu.find(k)){
					dsu.merge(k,k+1);
					cnt[q[i].c]=1;
				}
			}
		}
	}
	write(cnt.count());
	return 0;
}

D-Largest Remainder

状压DP卡过, d p [ s t ] [ p ] dp[st][p] dp[st][p]表示选取集合st且模数为p时的最大数值。那么DP转移式为 d p [ s t ∣ ( 1 < < i ) ] [ j ] = m a x ( d p [ s t ∣ ( 1 < < i ) ] [ j ] , d p [ s t ] [ k ] ∗ 10 + a [ i ] ) dp[st|(1<<i)][j]=max(dp[st|(1<<i)][j],dp[st][k]*10+a[i]) dp[st(1<<i)][j]=max(dp[st(1<<i)][j],dp[st][k]10+a[i]),其中 j 是 ( k + a [ i ] ) j是(k+a[i])%10 j(k+a[i])的余数。

#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;

ll dp[1<<16][200];
int n,p,a[20];

signed main(){
	scanf("%d%d",&n,&p);
	//ll lim=1;for(int i=1;i<n;++i) lim*=10;
	for(int i=0;i<n;++i) scanf("%d",&a[i]);
	memset(dp,-1,sizeof(dp));
	dp[0][0]=0;
	for(int s=0;s<(1<<n);++s){  //枚举状态 
		for(int i=0;i<n;++i){ //在未选集合中选一个数 
			if((s>>i)&1) continue;
			for(int j=0;j<p;++j){ //枚举模数 
				if(dp[s][j]==-1) continue;
				ll nxt=(j*10ll+a[i])%p; //下一个模数 
				dp[s|(1ll<<i)][nxt]=max(dp[s|(1ll<<i)][nxt],dp[s][j]*10ll+a[i]);
			}
		}
	}
	for(int i=p-1;i>=0;--i){
		if(dp[(1ll<<n)-1][i]!=-1){
			printf("%lld\n",dp[(1ll<<n)-1][i]);
			break;
		}
	}
	return 0;
}

I-Seesaw

当我们交换 a i a_i ai b j b_j bj时(题目要求 a i = = 1 且 b j = = 2 a_i==1且b_j==2 ai==1bj==2),左右的重量差值会减少 i + j i+j i+j,题意转化为从左右分别选取 k k k个物品使得左右选取的质量之和为重量差值 s u m sum sum。那么我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个物品达到重量 j j j的方案是否可行,就可以用bitset优化。转移方程为 d p [ j ] ∣ = d p [ j − a [ i ] ] < < 1 dp[j]|=dp[j-a[i]]<<1 dp[j]=dp[ja[i]]<<1。注意边界条件,sum<0和sum太大都要直接判结果,我因为这个re麻了。

#include<bits/stdc++.h>
using namespace std;
const int N=201,M=50001;

int t,n,a[N],b[N];
bitset<N>dp1[M],dp2[M];

int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}

signed main(){
	t=read();
	while(t--){
		n=read();
		int sum=0,cnt1=0,cnt2=0;
		for(int i=1,x;i<=n;++i){
			x=read();
			sum-=i*x;
			if(x==1) a[++cnt1]=i;
		}
		for(int i=1,x;i<=n;++i){
			x=read();
			sum+=i*x;
			if(x==2) b[++cnt2]=i;
		}
		if(sum<0||sum>=M){
			puts("-1");
			continue;
		}
		dp1[0][0]=1;dp2[0][0]=1;
		for(int i=1;i<=cnt1;++i){
			for(int j=sum;j>=a[i];--j){
				dp1[j]|=(dp1[j-a[i]]<<1);
			}
		}
		for(int i=1;i<=cnt2;++i){
			for(int j=sum;j>=b[i];--j){
				dp2[j]|=(dp2[j-b[i]]<<1);
			}
		}
		int ans=-1;
		for(int k=0;k<=min(cnt1,cnt2);++k){
			for(int i=0;i<=sum;++i){
				int j=sum-i;
				if(dp1[i][k]&&dp2[j][k]){
					ans=k;
					break;
				}
			}
			if(ans!=-1) break;
		}
		for(int i=0;i<=sum;++i) dp1[i].reset(),dp2[i].reset();
		printf("%d\n",ans);
	}
	return 0;
}

J-Transportation Network

英语阅读题,算出样例之后直觉得到一个结论就是:最终生成的树是点集 S S S的点全部和0号点相连,那么有 p − ∣ s ∣ p-|s| ps U U U集的点与0号点相连,剩下的所有 U U U点全部连其中一个 S S S点即可。那么树上有(①0号点;②与0号点相连且无儿子的S点;③与0号点相连且去U点相连的S点;④与0号点相连的U点;⑤挂在S点的U点)五个部分,跑五遍dfs即可直接统计距离和即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+7;

struct E{
	int v,w,nxt;
}e[N<<2];
int head[N],cnt=0;
inline void addedge(int u,int v,int w){
	e[++cnt]=(E){v,w,head[u]};head[u]=cnt;
}

int vis[N],dis[N];
void dfs(int x,int d){
	vis[x]=1;dis[x]=d;
	for(int i=head[x];i;i=e[i].nxt){
		int to=e[i].v,w=e[i].w;
		if(!vis[to]){
			dfs(to,d+w);
		}
	}
}

int t,n,s,p,h[N],is[N];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		cin>>n>>s>>p;
		for(int i=0;i<=n;++i) head[i]=dis[i]=vis[i]=is[i]=0;
		cnt=0;
		int numu=0,numsu=0,f1=0,f2=0;
		for(int i=1;i<=s;++i){
			cin>>h[i];
			addedge(0,h[i],1);
			addedge(h[i],0,1);
			is[h[i]]=1; //与0直接相连的s点 
		}
		for(int i=1,j=s+1;i<n;++i){
			if(is[i]) continue;
			if(j<=p){
				++j;
				addedge(0,i,2);
				addedge(i,0,2);
				f1=i;  //直接与0相连的u点
				++numu;
			}else{
				addedge(h[1],i,1);
				addedge(i,h[1],1);
				f2=i; ++numsu;  //与h[1]直接相连的u点 
			}
		}
		dfs(0,0);
		int ans=0,tmp=0;
		for(int i=0;i<n;++i) ans+=dis[i]; //①每个点到0的距离
		if(f1){ //②与0直接相邻的u点
			tmp=0;
			for(int i=0;i<n;++i) vis[i]=0;
			dfs(f1,0);
			for(int i=0;i<n;++i) tmp+=dis[i];
			ans+=tmp*numu;
		}
		if(s>1){  //③与0直接相连且没有儿子的s点
			tmp=0;
			for(int i=0;i<n;++i) vis[i]=0;
			dfs(h[2],0);
			for(int i=0;i<n;++i) tmp+=dis[i];
			ans+=tmp*(s-1);
		}
		for(int i=0;i<n;++i) vis[i]=0;
		tmp=0;
		dfs(h[1],0); //④h[1]的答案与h[1]相连的u点的答案 
		for(int i=0;i<n;++i) tmp+=dis[i];
		ans+=tmp;
		if(f2){
			tmp=0;
			for(int i=0;i<n;++i) vis[i]=0;
			dfs(f2,0);
			for(int i=0;i<n;++i) tmp+=dis[i];
			ans+=tmp*numsu; 
		}
		cout<<ans<<"\n";
	}
	return 0;
}

M-Escaping the Foggy Forest

枚举每一个点,判断四个方向是否存在一个合法就可以了。

#include <bits/stdc++.h>
using namespace std;
int n,m,s,f,r,a[105][105];
const int dx[]={-1,0,1,0},dy[]={0,1,0,-1}; 

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cin>>a[i][j];
		}
	}
	cin>>s>>f>>r;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(a[i][j]!=s) continue;
			bool flag=0;
			for(int d=0;d<4;++d){
				if(a[i+dx[d]][j+dy[d]]==f&&a[i+dx[(d+1)%4]][j+dy[(d+1)%4]]==r){
					flag=1;
				}
			}
			if(flag) cout<<i-1<<" "<<j-1<<"\n";
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RWLinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值