• CSP-S2021题解


    廊桥分配

    description

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

    solution

    将国内区和国际区分开考虑,最后再将答案合并。容易发现对于一架飞机如果在有(i) 个廊桥时找到了位置,那么有(>i) 个廊桥时它也能找到位置并且位置不变。因此从小到大枚举廊桥个数,每次根据先到先得的原则选择不相交的区间即可。使用set进行维护,复杂度(mathcal O(nlog 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
    ",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即可。三种规则生成的字符串都是互不重复的,因此对三种规则分别转移即可。直接转移会在第二个规则时将( exttt{()()()}) 这种情况多次计算,因此在区间([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
    ",f[1][n]);
    	return 0;
    }
    

    回文

    description

    有一个长度为(2n) 的双端队列,其中(1sim n) 都恰好出现了2次。现在要求每次操作从左侧/右侧弹出一个数依次排列形成序列(b) ,要求(b) 是回文的。如果无法做到,输出-1,否则输出字典序最小的方案数。(nle 5 imes 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,mle 500,sum kle 50)

    solution

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

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

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

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

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

    时间复杂度:(mathcal O(k^3+knmlog 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
    ",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;
    }
    
    NO PAIN NO GAIN
  • 相关阅读:
    windows下python开发环境搭建
    看看两年前的我
    网络函数[00]函数总述
    网络函数[04]connect解析
    网络函数[08]网络读取函数解析
    网络函数[01]套接口地址图解
    网络函数[13]
    网络函数[07]accept解析
    网络函数[10]shutdown解析
    网络函数[14]
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/15464323.html
Copyright © 2020-2023  润新知