CSP2021 题解

廊桥分配

考虑一个重要结论:对廊桥进行标号,每次将飞机加入廊桥时自动加入编号最小的廊桥,答案一定不会变化。

在此基础上,我们可以发现新增一个廊桥时,只会在新增的廊桥上增加飞机,不会影响其他廊桥上的飞机。因此,我们可以假设有无限个廊桥,那么一架飞机如果停在了第 \(i\) 座廊桥上,则只要廊桥数量 \(\ge i\) 它就总能找到位置。因此,我们只需要用一个堆来模拟无限廊桥的过程就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m1,m2,d[N],cnt,pos[N],tp[N],p[N],ct1[N],ct2[N];
struct line{
	int l,r;
}a[N],b[N];

int main(){
	scanf("%d%d%d",&n,&m1,&m2);
	for(int i=1;i<=m1;++i)
		scanf("%d%d",&a[i].l,&a[i].r),d[++cnt]=a[i].l,d[++cnt]=a[i].r;
	for(int i=1;i<=m2;++i)
		scanf("%d%d",&b[i].l,&b[i].r),d[++cnt]=b[i].l,d[++cnt]=b[i].r;
	sort(d+1,d+cnt+1);
	cnt=unique(d+1,d+cnt+1)-d-1;
	for(int i=1;i<=m1;++i){
		a[i].l=lower_bound(d+1,d+cnt+1,a[i].l)-d;
		a[i].r=lower_bound(d+1,d+cnt+1,a[i].r)-d;
		pos[a[i].l]=i;tp[a[i].l]=1;
		pos[a[i].r]=i;tp[a[i].r]=2;
	}
	priority_queue<int,vector<int>,greater<int> > q;
	for(int i=1;i<=m1;++i) q.push(i);
	for(int i=1;i<=cnt;++i){
		if(!tp[i]) continue;
		if(tp[i]==1){
			p[pos[i]]=q.top();ct1[p[pos[i]]]++;
			q.pop();
		}
		else q.push(p[pos[i]]);
	}
	while(!q.empty()) q.pop();
	for(int i=1;i<=m2;++i) q.push(i);
	for(int i=1;i<=m2;++i){
		b[i].l=lower_bound(d+1,d+cnt+1,b[i].l)-d;
		b[i].r=lower_bound(d+1,d+cnt+1,b[i].r)-d;
		pos[b[i].l]=i;tp[b[i].l]=3;
		pos[b[i].r]=i;tp[b[i].r]=4;
	}
	for(int i=1;i<=cnt;++i){
		if(tp[i]<3) continue;
		if(tp[i]==3){
			p[pos[i]]=q.top();ct2[p[pos[i]]]++;
			q.pop();
		}
		else q.push(p[pos[i]]);
	}
	
	int ans=0,now=0;
	for(int i=1;i<=n;++i) now+=ct1[i];ans=now;
	for(int i=n-1,j=1;i>=0;--i,++j){
		now-=ct1[i+1];now+=ct2[j];
		ans=max(ans,now);
	}
	printf("%d\n",ans);
	return 0;
}

括号序列

题目很明显是一道区间 DP,一眼看过去可能会觉得这就是在一个合法括号序列的每两个括号之间都可以加 \(\le k\) 个 * 号,但手玩样例会发现在 \((A()B)\) 的情况下,\(A,B\) 不能同时放 * 号。

对此,我的做法是设了三个 \(dp\) 数组,\(f_{l,r},g_{l,r},h_{l,r}\) 分别表示 \([l,r]\) 区间组成 \(A、(A)、SA\) 的方案,其中 \(A\) 是一个合法序列,\(S\) 是由长度不超过 \(k\) 的 * 号组成的序列,然而就很容易进行 \(\mathcal O(n^3)\) 的转移了。

#include<bits/stdc++.h>
using namespace std;
const int N=510,mod=1e9+7;
char s[N];
int n,k,f[N][N],g[N][N],h[N][N];
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline bool pd(int x,char c){
	if(s[x]==c||s[x]=='?') return true;
	return false;
}
int main(){
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	for(int i=0;i<=n+1;++i) f[i][i-1]=g[i][i-1]=h[i][i-1]=1;
	for(int len=2;len<=n;++len){
		for(int l=1;l+len-1<=n;++l){
			int r=l+len-1;
			//g[l][r]
			if(pd(l,'(')&&pd(r,')')){
				for(int i=l;i<r&&i<=l+k;++i){
					inc(g[l][r],f[i+1][r-1]);
					if(!pd(i+1,'*')) break;
				}
				if(pd(r-1,'*')){
					for(int i=r-1;i>l+1&&i>=r-k;--i){
						inc(g[l][r],f[l+1][i-1]);
						if(!pd(i-1,'*')) break;
					}
				}
			}
			//f[l][r]
			if(pd(l,'(')){
				for(int mid=l+1;mid<=r;++mid){
					if(!pd(mid,')')) continue;
					inc(f[l][r],1ll*g[l][mid]*h[mid+1][r]%mod);
				}
			}
			//h[l][r]
			for(int i=l;i<=r&&i<=l+k;++i){
				inc(h[l][r],f[i][r]);
				if(!pd(i,'*')) break;
			}
		}
	}
	printf("%d\n",f[1][n]);
	return 0;
}

回文

考虑进行第一步操作后,就同时确定了最后一个取出的数的位置 \(p\),那么 \(p\) 左侧的数必须向左取,\(p\) 右侧的数必须向右取,将这两部分分别看成两个栈,那么每一步取出的必须是某一个栈的栈顶,并且它的另一个位置必须是某一个栈的栈底,那么只需要贪心的优先删左边,删不了就删右边即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
map<pair<int,int>,int>mp;
int T,n,a[N],L[N],R[N],ct[N];
bool fl=0;
char s[N];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
	return x*f;
}
inline bool solve(char c,int i,int j,int ql,int qr){
	int now=0;
	s[++now]=c;
	while(i<ql){
		while(j>qr&&ql!=R[a[i]]+1&&qr!=R[a[i]]-1){
			s[++now]='R';
			if(ql==L[a[j]]+1) s[(n<<1)-now+1]='L',ql--;
			else if(qr==L[a[j]]-1) s[(n<<1)-now+1]='R',qr++;
			else return false;
			j--;
		}
		if(i>=ql) break;
		if(ql!=R[a[i]]+1&&qr!=R[a[i]]-1) return false;
		if(ql==R[a[i]]+1) s[++now]='L',ql=R[a[i]],s[(n<<1)-now+1]='L';
		else s[++now]='L',qr=R[a[i]],s[(n<<1)-now+1]='R';
		i++;
	}
	while(j>qr){
		s[++now]='R';
		if(now!=1){
			if(ql==L[a[j]]+1) s[(n<<1)-now+1]='L',ql--;
			else if(qr==L[a[j]]-1) s[(n<<1)-now+1]='R',qr++;
			else return false;
		}
		else ql=qr=L[a[j]];
		j--;
	}
	s[n<<1]='L';
	for(int i=1;i<=(n<<1);++i) putchar(s[i]);
	puts("");
	return true;
}
int main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;++i) L[i]=R[i]=-1;
		for(int i=1;i<=(n<<1);++i){
			a[i]=read();
			if(L[a[i]]==-1) L[a[i]]=i;
			else R[a[i]]=i;
		}
		if(solve('L',2,n<<1,R[a[1]],R[a[1]])) continue;
		else if(solve('R',1,(n<<1)-1,L[a[n<<1]],L[a[n<<1]])) continue;
		else puts("-1");
	}
	return 0;
}

交通规划

首先考虑 \(k=2\) 的时候,此时我们固定了两条附加边的颜色,如果它们颜色相同则答案显然是 \(0\),否则我们应当将所有点分成两种颜色的连通块,也就相当于在对偶图上连一条最短路径将两个点分开,这是可以通过最短路完成的。

\(k>2\) 时,我们仍然希望只连接颜色相同的点之间的边,将每个点分成若干个不同颜色的连通块后,满足所有不同颜色的附加边不在同一个连通块内。转化成对偶图后,这就相当于是在下图中两种颜色的点之间连边使得所有相邻的不同颜色点都被隔开。

CSP2021 题解

于是问题就转化为了在图中蓝色连续段和橙色连续段之间连边,将蓝色与橙色两两匹配,匹配代价就是它们的最短路。可以发现此时如果我们连接交叉的两条边 \((x_1,y_1),(x_2,y_2)\),那么一定可以通过交换成 \((x_1,y_2),(x_2,y_1)\) 而使答案不会更劣。

因此我们只用连接的边之间一定只会包含或相离,可以对此进行区间 \(DP\),最终总复杂度为 \(\mathcal O(k^3+knm\log nm)\)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=510;
int n,m,T,cnt,first[N*N],pos[N<<2],vis[N*N],ve[N];ll dis[N*N];
pair<int,int> a[N];
inline int id(int x,int y){return (x-1)*(m+1)+y;}
struct node{
	int v,w,nxt;
}e[N*N*4];
struct pt{
	int x,p,c;
}p[N];
bool operator <(const pt &a,const pt &b){return a.p<b.p;}
inline void add(int u,int v,int w){
	e[++cnt]=(node){v,w,first[u]};first[u]=cnt;
	e[++cnt]=(node){u,w,first[v]};first[v]=cnt;	
}
inline void dij(int x){
	int all=(n+1)*(m+1);
	memset(dis+1,0x3f,sizeof(ll)*(all));
	memset(vis+1,0,sizeof(int)*(all));
	priority_queue<pair<ll,int> > q;
	dis[x]=0;q.push(make_pair(0,x));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;vis[u]=1;
		for(int i=first[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[u]+e[i].w<dis[v]){
				dis[v]=dis[u]+e[i].w;
				q.push(make_pair(-dis[v],v));
			} 
		}
	}
}
ll f[N][N],d[N][N];
int pre[N],nxt[N];
int main(){
	scanf("%d%d%d",&n,&m,&T);
	for(int i=1;i<n;++i) for(int j=1,w;j<=m;++j) scanf("%d",&w),add(id(i+1,j),id(i+1,j+1),w);
	for(int i=1;i<=n;++i) for(int j=1,w;j<m;++j) scanf("%d",&w),add(id(i,j+1),id(i+1,j+1),w);
	for(int i=1;i<=m;++i) add(id(1,i),id(1,i+1),0),pos[i]=cnt-1;
	for(int i=1;i<=n;++i) add(id(i,m+1),id(i+1,m+1),0),pos[i+m]=cnt-1;
	for(int i=1;i<=m;++i) add(id(n+1,m+2-i),id(n+1,m+1-i),0),pos[i+m+n]=cnt-1;
	for(int i=1;i<=n;++i) add(id(n+2-i,1),id(n+1-i,1),0),pos[i+m+n+m]=cnt-1;
	int all=n+m+n+m;
	while(T--){
		int k;scanf("%d",&k);
		for(int i=0;i<k;++i){
			scanf("%d%d%d",&p[i].x,&p[i].p,&p[i].c);
			e[pos[p[i].p]].w=e[pos[p[i].p]+1].w=p[i].x;
		}
		sort(p,p+k);
		int tot=0;
		for(int i=0;i<k;++i)
			if(p[i].c!=p[(i+1)%k].c){
				int j=p[i].p,&x=ve[tot];
				if(j<=m) x=id(1,j+1);
				else if(j<=m+n) x=id(j-m+1,m+1);
				else if(j<=m+n+m) x=id(n+1,m+n+m+1-j);
				else x=id(m+n+m+n+1-j,1);
				tot++;
			}
		for(int i=0;i<tot;++i){
			dij(ve[i]);
			for(int j=i+1;j<tot;++j){
				d[i][j]=dis[ve[j]]; 
				d[j][i]=d[i][j];
			}
		}
		for(int i=0;i<tot;++i)
			nxt[i]=(i==tot-1)?0:i+1,pre[i]=i?i-1:tot-1;
		for(int len=2;len<=tot;len+=2){
			for(int l=0;l<tot;++l){
				int r=(l+len-1)%tot;
				f[l][r]=1e15;
				for(int j=nxt[l];;j=nxt[nxt[j]]){
					f[l][r]=min(f[l][r],(j==nxt[l]?0:f[nxt[l]][pre[j]])+d[l][j]+(j==r?0:f[nxt[j]][r]));
					if(j==r) break;
				}
			}
		}
		printf("%lld\n",f[0][tot-1]);
		for(int i=0;i<k;++i)
			e[pos[p[i].p]].w=e[pos[p[i].p]+1].w=0;
	}
	return 0;
}
上一篇:QuantLib 金融计算——一个使用 ActualActual 时遇到的陷阱


下一篇:2021-11-11