CSP-S2021题解

廊桥分配

description

机场分国内区和国际区,分别有\(m_1,m_2\) 架飞机会到来,每架飞机停在机场的时间为\([a_i,b_i]\) 。每架飞机来到机场后会选择在廊桥/远机位。飞机会优先停靠廊桥,而廊桥使用先到先得,即如果某架飞机到达时存在空闲的廊桥则会停靠,否则停靠远机位。现在总共\(n\) 个廊桥,要求进行合理的分配使得停靠廊桥的飞机尽量多。\(1\le n,m_1+m_2\le 10^5\)

solution

将国内区和国际区分开考虑,最后再将答案合并。容易发现对于一架飞机如果在有\(i\) 个廊桥时找到了位置,那么有\(>i\) 个廊桥时它也能找到位置并且位置不变。因此从小到大枚举廊桥个数,每次贪心地选择不相交的区间即可(可以通过交换证明最优解会被包含)。使用set进行维护,复杂度\(\mathcal O(n\log n)\) 。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5; 
int n,m[2],res[2][N];
set<pair<int,int> >s[2];
int main()
{
	scanf("%d%d%d",&n,&m[0],&m[1]);
	for(bool o:{0,1})
		for(int i=1,l,r;i<=m[o];++i)
			scanf("%d%d",&l,&r),s[o].insert(make_pair(l,r));
	for(bool o:{0,1})
		for(int i=1;i<=n;++i)
		{
			res[o][i]=res[o][i-1];
			if(s[o].empty())continue;
			int pre=0;
			while(1)
			{
				auto p=s[o].upper_bound(make_pair(pre,0));
				if(p==s[o].end())break;
				++res[o][i];pre=(*p).second;
				s[o].erase(p);
			}
		}
	int ans=0;
	for(int i=0;i<=n;++i)ans=max(ans,res[0][i]+res[1][n-i]);
	printf("%d\n",ans);
	return 0;
}

括号序列

description

以如下的方式定义“超级括号序列”:

  1. ()(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k 字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);
  2. 如果字符串 AB 均为符合规范的超级括号序列,那么字符串 ABASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)(SA)(AS) 均为符合规范的超级括号序列。
  4. 所有符合规范的超级括号序列均可通过上述 3 条规则得到。

现在需要对一个包含且仅包含(,),?,*的字符串\(|S|\) 求出有多少中填法使得其得到的字符串是一个超级括号序列。

有模数。\(|S|\le 500\) 。

solution

区间dp即可。三种规则生成的字符串都是互不重复的,因此对三种规则分别转移即可。直接转移会在第二个规则时将\(\texttt{()()()}\) 这种情况多次计算,因此在区间\([l,r]\) 枚举断点\(k\) 时必须钦定\([l,k]\) 这段区间\(l\) 处的左括号和\(k\) 处的右括号匹配即可,需要新增一种dp状态。再使用前缀和进行优化就能做到\(\mathcal O(n^3)\) 了。

code

#include<bits/stdc++.h>
using namespace std;
const int N=505,mod=1e9+7;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline void inc(int&x,int y){x=x+y>=mod?x+y-mod:x+y;}
int n,k,f[N][N],s[N],g[N][N],sum[N][N];char ch[N];
inline bool ok(int l,int r){return s[r]-s[l-1]==0;}
inline bool okl(int p){return ch[p]=='?'||ch[p]=='(';}
inline bool okr(int p){return ch[p]=='?'||ch[p]==')';}
inline int S(int pl,int pr,int r){return dec(sum[pl][r],sum[pr+1][r]);}
int main()
{
	scanf("%d%d",&n,&k);
	scanf("%s",ch+1);
	for(int i=1;i<=n;++i)s[i]=s[i-1]+(ch[i]=='('||ch[i]==')');
	for(int len=2;len<=n;++len)
		for(int l=1,r=l+len-1;r<=n;++l,++r)
		{
			int&d=f[l][r],&e=g[l][r];
			if(len==2&&okl(l)&&okr(r))++d,++e;
			if(3<=len&&len<=k+2&&okl(l)&&okr(r)&&ok(l+1,r-1))++d,++e;
			for(int p=l,q=p;p<r;++p)
			{
				inc(d,1ll*g[l][p]*f[p+1][r]%mod);
				q=max(q,p);
				while(q<r&&ok(q+1,q+1))++q;
				if(p+2<=r&&p<q)inc(d,1ll*g[l][p]*S(p+2,min({r,q+1,p+k+1}),r)%mod);
			}
			if(len>2&&okl(l)&&okr(r))
			{
				int res=0;
				inc(res,f[l+1][r-1]);
				for(int t=1;t<=k&&l+t<r;++t)
					if(ok(l+1,l+t))inc(res,f[l+t+1][r-1]);
					else break;
				for(int t=1;t<=k&&r-t>l;++t)
					if(ok(r-t,r-1))inc(res,f[l+1][r-t-1]);
					else break;
				inc(d,res),inc(e,res);
			}
			sum[l][r]=add(sum[l+1][r],d);
		}
	printf("%d\n",f[1][n]);
	return 0;
}

回文

description

有一个长度为\(2n\) 的双端队列,其中\(1\sim n\) 都恰好出现了2次。现在要求每次操作从左侧/右侧弹出一个数依次排列形成序列\(b\) ,要求\(b\) 是回文的。如果无法做到,输出-1,否则输出字典序最小的方案数。\(n\le 5\times 10^5\)

solution

显然必然存在一个分界点满足分界点左侧都是从左端依次弹出,分界点右侧都是从右端依次弹出。枚举这个分界点,然后左右两侧的数各形成一个栈。每次需要从栈顶取出元素\(x\) ,那么必须满足和\(x\) 值相同的元素必须位于某个栈的栈底。然后将这两个数都删去继续做即可。由于要求字典序最小,因此每次需要贪心的选择。然而现在仍然是\(\mathcal O(n^2)\) 的。注意到两个栈的栈顶都是确定的,因此对应的栈底也有强限制,这使得合法的分界点只有4个,分别枚举做即可。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,a[N],s[N],t[N];bool ans[N],fl,res[N];
inline void work(int p)
{
	int x=0,y=0;
	for(int i=1;i<=p;++i)s[++x]=a[i];
	for(int i=2*n;i>p;--i)t[++y]=a[i];
	int c=1,d=1;
	for(int i=1;i<=n;++i)
	{
		int p1=i,p2=2*n-i+1;
		if(c<x&&s[c]==s[x])res[p1]=res[p2]=0,++c,--x;
		else if(c<=x&&d<=y&&s[c]==t[y])res[p1]=0,res[p2]=1,++c,--y;
		else if(c<=x&&d<=y&&t[d]==s[x])res[p1]=1,res[p2]=0,++d,--x;
		else if(d<y&&t[d]==t[y])res[p1]=res[p2]=1,++d,--y;
		else return;
	}
	fl=1;bool ok=0;
	for(int i=1;i<=2*n;++i)
		if(res[i]<ans[i]){ok=1;break;}
		else if(res[i]>ans[i]){ok=0;break;}
	if(ok)for(int i=1;i<=2*n;++i)ans[i]=res[i];
}
int main()
{
	int T=read();
	while(T-->0)
	{
		n=read();
		for(int i=1;i<=2*n;++i)a[i]=read(),ans[i]=1;
		int p1=0,p2=0;
		for(int i=2;i<=2*n;++i)if(a[i]==a[1]){p1=i;break;}
		for(int i=1;i<2*n;++i)if(a[i]==a[2*n]){p2=i;break;}
		fl=0;work(p1),work(p1-1);work(p2),work(p2-1);
		if(!fl){puts("-1");continue;}
		for(int i=1;i<=2*n;++i)putchar(ans[i]?'R':'L');puts("");
	}
	return 0;
}

交通规划

description

有一个由\(n\) 条横直线,\(m\) 条纵直线互相相交形成的网格图,网格中的边有边权。

现在从网格边界向外延伸的\(2(n+m)\) 条射线中,有\(k\) 条出现了颜色为黑/白的点,并且一直它和最近的网格点之间的边权大小。现在需要给网格点进行染色,使得左右两端染色不同的边的边权之和最小。

\(n,m\le 500,\sum k\le 50\)

solution

\(k=2\) 的时候容易发现是最小割。根据狼抓兔子的那套理论,可以将平面图最小割转化为对偶图最短路。

\(k>2\) 的时候考虑其本质是什么,如下图所示:

CSP-S2021题解

对于相邻且颜色不同的点我们需要用割边将其隔开,而注意到一条割边可以分隔两对这样的点,因此对这些间隔(图中绿色部分)一一匹配可以达到最优。而匹配的代价就是原图最小割,即对偶图上的最短路。

CSP-S2021题解

而对于两条割线相交的情况(上图黑线),我们可以用蓝线进行代替,代价相同且效果相同。即我们可以通过调整使得最优解的任意两条割线都不相交。

因此对于每组询问只需要处理出间隔间的最短路,然后区间dp即可。dp转移时只需考虑左侧端点的匹配节点就行。

时间复杂度:\(\mathcal O(k^3+knm\log nm)\) 。

code

#include<bits/stdc++.h>
using namespace std;
const int N=505,inf=0x3f3f3f3f;
int n,m,T,k;
int tot=1,fi[N*N],ne[N*N*4],to[N*N*4],w[N*N*4],op[N*4];
struct node{int pos,val,c;}t[N];
inline int id(int x,int y){return x*(m+1)+y+1;}
inline void add(int x,int y,int s)
{
	ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,w[tot]=s;
	ne[++tot]=fi[y],fi[y]=tot,to[tot]=x,w[tot]=s;
}
int key[N*8],dis[N*N],res[105][105],f[105][105];bool done[N*N];
inline int gp(int x)
{
	if(x<=m)return id(0,x);x-=m;
	if(x<=n)return id(x,m);x-=n;
	if(x<=m)return id(n,m-x);x-=m;
	return id(n-x,0);
}
inline void dij(int S)
{
	typedef pair<int,int> pii;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	fill(dis+1,dis+id(n,m)+1,inf);
	fill(done+1,done+id(n,m)+1,0);
	q.push(make_pair(dis[S]=0,S));
	while(!q.empty())
	{
		int u=q.top().second;q.pop();
		if(done[u])continue;done[u]=1;
		for(int i=fi[u];i;i=ne[i])
		{
			int v=to[i];
			if(dis[v]>dis[u]+w[i])
			{
				dis[v]=dis[u]+w[i];
				q.push(make_pair(dis[v],v));
			}
		}
	}
}
inline void work()
{
	scanf("%d",&k);int cc=0;
	for(int i=1;i<=k;++i)
		scanf("%d%d%d",&t[i].val,&t[i].pos,&t[i].c);
	sort(t+1,t+k+1,[&](const node&x,const node&y){return x.pos<y.pos;});
	for(int i=1;i<=k;++i)
	{
		int now=t[i].pos;
		w[op[now]]=w[op[now]^1]=t[i].val;
		int j=i==k?1:i+1;
		if(t[i].c==t[j].c)continue;
		key[++cc]=gp(t[i].pos);
	}
	if(!cc)puts("0");
	else
	{
		for(int i=1;i<=cc;++i)
		{
			dij(key[i]);
			for(int j=1;j<=cc;++j)
				res[i][j]=dis[key[j]];
		}
		for(int i=1,j=cc+1;i<=cc;++i,++j)key[j]=key[i];
		cc<<=1;int mm=cc>>1;
		for(int i=1;i<=cc;++i)f[i][i-1]=0;
		for(int len=2;len<=cc;len+=2)
			for(int l=1,r=l+len-1;r<=cc;++l,++r)
			{
				int&d=f[l][r];d=inf;
				for(int t=l+1;t<=r;t+=2)
					d=min(d,f[l+1][t-1]+f[t+1][r]+res[l>mm?l-mm:l][t>mm?t-mm:t]); 
			}
		int ans=inf;
		for(int i=1,j=mm;j<=cc;++i,++j)ans=min(ans,f[i][j]);
		printf("%d\n",ans);
	}
	for(int i=1;i<=k;++i)
	{
		int now=t[i].pos;
		w[op[now]]=w[op[now]^1]=0;
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&T);
	for(int i=1;i<n;++i)
		for(int j=1,t;j<=m;++j)
			scanf("%d",&t),add(id(i,j-1),id(i,j),t);
	for(int i=1;i<=n;++i)
		for(int j=1,t;j<m;++j)
			scanf("%d",&t),add(id(i-1,j),id(i,j),t);
	for(int i=1;i<=m;++i)add(id(0,i-1),id(0,i),0),op[i]=tot;
	for(int i=1;i<=n;++i)add(id(i-1,m),id(i,m),0),op[m+i]=tot;
	for(int i=1;i<=m;++i)add(id(n,m-i+1),id(n,m-i),0),op[m+n+i]=tot;
	for(int i=1;i<=n;++i)add(id(n-i+1,0),id(n-i,0),0),op[m+n+m+i]=tot;
	while(T--)work();
	return 0;
}
上一篇:NYOJ 570 解题报告


下一篇:《具体数学》学习笔记