把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 031 解题报告(A~E)

点此进入比赛

\(A\):Colorful Subsequence(点此看题面

大致题意: 给定一个序列,问有多少个子序列满足子序列中的字符各不相同。

刚看完题面差点以为自己连\(A\)题都不会了,但仔细一想发现果然是道送分题。

\(f_i\)表示在前\(i\)个字符中进行选择的答案,并在枚举\(i\)的同时用\(c_x\)表示\(x\)在前\(i-1\)个字符中的出现次数。

考虑在\(f_{i-1}\)中,是否选择\(s_i\)会有\(c_{s_i}+1\)种情况(选择了\(c_{s_i}\)个中的一个,或是不选),而这并不会影响其他字符的选择。

也就是说,没有选择过\(s_i\)的方案数是\(\frac1{c_{s_i}+1}\times f_{i-1}\)

那么实际上完全不需要开数组,直接用一个变量\(f\),每次令\(f=f\times(\frac1{c_{s_i}+1}+1)\),并给\(c_{s_i}\)\(1\)就可以了。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
using namespace std;
int n,c[30];char s[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int main()
{
	RI i,f=1;for(scanf("%d%s",&n,s+1),i=1;i<=n;++i) f=1LL*f*(QP(c[s[i]&31]+1,X-2)+1)%X,++c[s[i]&31];//递推转移
	return printf("%d\n",f-1),0;//减去空串的情况
}

\(B\):Reversi(点此看题面

大致题意: 给定一个序列,可进行任意次操作,每次选择\(i,j\)满足\(a_i=a_j\)\([i,j]\)的数都变成\(a_i\)。求可能得到多少种不同的序列。

同样的\(SB\)题,很容易想到两个结论:

  • 对于\(i\),只需考虑上一个\(a_{lst}=a_i\)\(lst\)就可以了,因为大的区间完全可以分成若干个小区间。
  • 一种可能的序列必然存在一种生成方式,满足每一个数最多被改变一次。

于是我们设\(f_i\)表示在前\(i\)个数中可能得到的序列数,\(lst\)表示上一个\(a_{lst}=a_i\)\(lst\),转移方程显然:

\[f_i=f_{i-1}+f_{lst} \]

注意,当\(lst=0\)(无法操作)或\(lst=i-1\)(操作无效,因为中间没有数,不会使序列改变)时不能转移。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define X 1000000007
using namespace std;
int n,lst[N+5],f[N+5];
int main()
{
	RI i,x;for(scanf("%d",&n),f[0]=i=1;i<=n;++i) scanf("%d",&x),//读入当前颜色
		f[i]=f[i-1],lst[x]&&lst[x]^(i-1)&&(f[i]=(f[i]+f[lst[x]])%X),lst[x]=i;//转移,更新该颜色最后出现的位置
	return printf("%d\n",f[n]),0;//输出答案
}

\(C\):Differ by 1 Bit(点此看题面

大致题意: 求一个\(0\sim2^n-1\)的排列\(P_i\),使得\(P_i\)\(P_{i+1}\)恰好有一位不同,且\(P_0=A,P_{2^n-1}=B\)

首先,由于\(2^n-1\)是奇数,因此若\(A\)\(B\)有偶数位不同则无解,否则必然有解。

考虑为了方便,我们只要构造一个排列\(P\),令\(P_0=0,P_{2^n-1}=A\oplus B\),并在最后给\(P\)中的所有数异或上\(A\)即可。

而构造方式可以看作是在一个\(n\)维的超立方体的顶点之间走路,找到一条哈密顿路径。

显然,我们可以先把\(n-1\)维的超立方体走完,然后再走到\(n\)维上去。

具体实现中,每次从\(A\oplus B\)的二进制表示中挑选出一个\(1\)(假设在第\(k\)位),先遍历第\(k\)位为\(0\)的所有点组成的\(n-1\)维超立方体,然后遍历第\(k\)位为\(1\)\(n-1\)维超立方体。

至于这两个\(n-1\)维的超立方体之间的连接点,实际上完全可以任选。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 17
using namespace std;
int n,lim,A,B,a[1<<N];I int C(RI x) {RI t=0;W(x) ++t,x&=x-1;return t;}
I void Paint(CI x,CI y,CI s)//超立方体上走路
{
	if(C(s^(lim-1))==1) return (void)printf("%d %d ",y,x^y);RI i,j;//边界直接输出
	for(i=0;i^n;++i) if(!((s>>i)&1)&&(x>>i&1)) for(j=0;j^n;++j) if(!((s>>j)&1)&&i^j)
		return Paint(1<<j,y,s|(1<<i)),Paint(x^(1<<i)^(1<<j),y^(1<<i)^(1<<j),s|(1<<i));//任选
}
int main()
{
	RI i,t;if(scanf("%d%d%d",&n,&A,&B),lim=1<<n,!(C(A^B)&1)) return puts("NO"),0;//判无解
	return puts("YES"),Paint(A^B,A,0),0;
}

\(D\):A Sequence of Permutations(点此看题面

大致题意: 给定两个\(1\sim n\)的排列\(p,q\),定义\(f(p,q)\)表示第\(p_i\)位的值是\(q_i\)的一个排列。已知\(a_1=p,a_2=q,a_i=f(a_{i-2},a_{i-1})(i>2)\),求\(a_k\)

首先给出关于置换的两种运算的定义:

  • \(p\times q\)\((p\times q)_i=p_{q_i}\),简写为\(pq\)。显然这一运算满足结合律但不满足交换律。
  • \(p'\)\(p_{p_i'}=i\),其实\(p'_{p_i}=i\)也一定成立。同时还有\(p\times p'=p'\times p=e\),其中\(e\)为排列\(\{1,2,...,n\}\),即单位元。

把两种运算结合起来又有一个新的公式:\((pq')'=qp'\)。这个公式可以扩展到多项,例如:\((p'pq'qp)'=p'q'qp'p\)。(相当于把式子翻转然后对于每一项改变\('\)的存在性)

有了这些前置知识,就方便我们大力找规律了:(这里已经把相邻的\(pp',qq'\)约去)

居中形式:

\[a_1=p\\a_2=q\\a_3=qp'\\a_4=qp'q'\\a_5=qp'q'pq'\\a_6=qp'q'ppq'\\a_7=qp'q'ppp'qpq'\\a_8=qp'q'pqp'qpq'\\a_9=qp'q'pqp'p'qpq' \]

左对齐形式:

\[\begin{align}&a_1=p\\&a_2=q\\&a_3=qp'\\&a_4=qp'q'\\&a_5=qp'q'pq'\\&a_6=qp'q'ppq'\\&a_7=qp'q'ppp'qpq'\\&a_8=qp'q'pqp'qpq'\\&a_9=qp'q'pqp'p'qpq'\end{align} \]

右对齐形式:

\[\begin{align}a_1=p&\\a_2=q&\\a_3=qp'&\\a_4=qp'q'&\\a_5=qp'q'pq'&\\a_6=qp'q'ppq'&\\a_7=qp'q'ppp'qpq'&\\a_8=qp'q'pqp'qpq'&\\a_9=qp'q'pqp'p'qpq'&\end{align} \]

仔细观察可以发现,令\(s=qp'q'p\),就有\(a_{n}=s\times a_{n-6}\times s'\),可以归纳证明。

中间的一项实际上就是\(a_{n\%6}\),两边的\(s\)\(s'\)可能有很多,但由于置换乘法满足结合律,故可快速幂高效求解。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,k;struct P//置换
{
	int a[N+5];I P(CI x=0) {for(RI i=1;i<=x;++i) a[i]=i;}
	I int& operator [] (CI x) {return a[x];}I int operator [] (CI x) Con {return a[x];}
 	I friend P operator ~ (Con P& A) {P B;for(RI i=1;i<=n;++i) B[A[i]]=i;return B;}//取反
	I friend P operator * (Con P& A,Con P& B) {P C;for(RI i=1;i<=n;++i) C[i]=A[B[i]];return C;}//乘法
}p,q,s,a[10],ans;
I P QP(P x,RI y) {P t=P(n);W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;}//快速幂
int main()
{
	RI i;for(scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&p[i]);for(i=1;i<=n;++i) scanf("%d",&q[i]);
	a[1]=p,a[2]=q,a[3]=a[2]*(~a[1]),a[4]=a[3]*(~a[2]),a[5]=a[4]*(~a[3]),a[6]=a[5]*(~a[4]);//预处理
	s=q*(~p)*(~q)*p,ans=QP(s,(k-1)/6)*a[(k-1)%6+1]*QP(~s,(k-1)/6);//根据规律求出最终置换
	for(i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);return 0;//输出答案
}

\(E\):Snuke the Phantom Thief(点此看题面

大致题意: 给定二维平面上\(n\)个珠宝,每个珠宝有一个价值。有若干限制,规定横坐标/纵坐标小于等于/大于等于\(x\)的珠宝中最多选择\(y\)个。求能获得的最大总价值。

一开始很\(naive\)地想到单纯形法,然而每个珠宝只能是选或不选(\(0/1\)),并不可以。

实际上这是一道网络流题。

考虑我们枚举最终选择了\(k\)个珠宝,以对横坐标的限制为例:

  • L:横坐标排名大于\(y\)的珠宝横坐标不能小于等于\(x\)
  • R:横坐标排名小于等于\(k-y\)的珠宝纵坐标不能大于等于\(y\)

于是就能求出这一维\(k\)个珠宝各自的坐标范围,把这个范围看作点,将其与符合该范围的珠宝连边。

由于是两维,所以一维向珠宝入点连边,一维由珠宝出点向其连边。

最后建来的图相当于被分为六层:超级源、横坐标范围、珠宝入点、珠宝出点、纵坐标范围、超级汇。

然后跑最大费用最大流即可。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 80
#define M 320
#define LL long long
#define INF 1e18
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,L[N+5],R[N+5],U[N+5],D[N+5];
struct Jewel {int x,y;LL v;}s[N+5];struct Data {char op;int x,y;}p[M+5];
template<int PS,int ES> class MaxCostMaxlow//费用流
{
	private:
		#define E(x) ((((x)-1)^1)+1)
		#define S (4*n+1)
		#define T (4*n+2)
		int ee,lnk[PS+5];struct edge {int to,nxt,F;LL C;}e[2*ES+5];
		int p[PS+5],IQ[PS+5],F[PS+5];LL C[PS+5];queue<int> q;
		I bool SPFA()//SPFA找增广路
		{
			RI i,k;for(i=0;i<=4*n+2;++i) F[i]=1e9,C[i]=-INF;q.push(S),C[S]=0;
			W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
			{
				if(!e[i].F||C[k]+e[i].C<=C[e[i].to]) continue;
				C[e[i].to]=C[k]+e[p[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
				!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
			}return F[T]!=1e9;
		}
	public:
		I void Clear() {ee=0,memset(lnk,0,sizeof(lnk));}//清空
		I void Add(CI x,CI y,CI f,Con LL& c)//建边
		{
			e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
			e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c;
		}
		I LL MCMF(RI k)
		{
			RI x;LL t=0;W(SPFA())
			{
				k-=F[T],t+=C[T]*F[T],x=T;
				W(x^S) e[p[x]].F-=F[T],e[E(p[x])].F+=F[T],x=e[E(p[x])].to;
			}return k?0:t;//如果流量与规定选择数量不同返回0
		}
};MaxCostMaxlow<4*N+2,3*N+2*N*N> F;
int main()
{
	RI i,j,k;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%lld",&s[i].x,&s[i].y,&s[i].v);
	for(scanf("%d",&m),i=1;i<=m;++i) cin>>p[i].op,scanf("%d%d",&p[i].x,&p[i].y);
	LL ans=0;for(i=1;i<=n;++i)//枚举最终选的珠宝数
	{
		for(j=1;j<=i;++j) L[j]=D[j]=0,R[j]=U[j]=100;for(j=1;j<=m;++j) switch(p[j].op)//枚举限制确定范围
		{
			case 'L':p[j].y<i&&Gmax(L[p[j].y+1],p[j].x+1);break;
			case 'R':p[j].y<i&&Gmin(R[i-p[j].y],p[j].x-1);break;
			case 'D':p[j].y<i&&Gmax(D[p[j].y+1],p[j].x+1);break;
			case 'U':p[j].y<i&&Gmin(U[i-p[j].y],p[j].x-1);break;
		}
		//1~n珠宝入点,n+1~2n珠宝出点,2n+1~3n横坐标范围,3n+1~4n纵坐标范围,4n+1/2超级源/汇
		for(j=2;j<=i;++j) Gmax(L[j],L[j-1]),Gmax(D[j],D[j-1]);
		for(j=i-1;j;--j) Gmin(R[j],R[j+1]),Gmin(U[j],U[j+1]);F.Clear();
		for(j=1;j<=n;++j) F.Add(j,n+j,1,s[j].v);for(j=1;j<=i;++j) F.Add(S,2*n+j,1,0),F.Add(3*n+j,T,1,0);//入点与出点;超级源/汇与坐标范围
		for(j=1;j<=i;++j) for(k=1;k<=n;++k) L[j]<=s[k].x&&s[k].x<=R[j]&&(F.Add(2*n+j,k,1,0),0);//横坐标范围与入点
		for(j=1;j<=i;++j) for(k=1;k<=n;++k) D[j]<=s[k].y&&s[k].y<=U[j]&&(F.Add(n+k,3*n+j,1,0),0);//出点与纵坐标范围
		ans=max(ans,F.MCMF(i));//统计最优答案
	}return printf("%lld\n",ans),0;
}
posted @ 2020-08-20 18:56  TheLostWeak  阅读(244)  评论(0编辑  收藏  举报