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][r−1]+[两边交换后相等])。
#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==1且bj==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[j−a[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| p−∣s∣个 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;
}