AtCoder Grand Contest 031

Preface

这场后面题目好难啊,C就开始思博了


A - Colorful Subsequence

考虑DP,\(f_i\)表示前\(i\)个数的答案,考虑如何去除重复的限制

对于当前的\(i\),设之前\(s_j=s_i\)\(j\)\(c\)个,显然我们在这\(c+1\)个数里只能选出一个来,因此转移\(f_i+=\frac{f_{i-1}}{c+1}\)

然后我们发现数组都不用开,直接\(O(n)\)做即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7;
int n,c[N],ans; char s[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
	RI i,j; for (scanf("%d%s",&n,s+1),ans=i=1;i<=n;++i)
	++c[s[i]-'a'],(ans+=1LL*ans*quick_pow(c[s[i]-'a'])%mod)%=mod;
	return printf("%d",ans-1),0;
}


B - Reversi

\(f_i\)表示前\(i\)个石子染色的方案数,很容易发现我们对于某种颜色,我们只需要找出这个颜色的前驱

考虑给这段区间染色,显然染过之后这段区间内的数都不会再染色了,因此我们可以直接从前驱转移来

但这样可能会无法从前驱的前驱转移来,因此我们每次把同种颜色的贡献一起累加即可

注意特判前驱不存在以及相同的颜色相邻的情况,复杂度\(O(n)\)

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int n,x,pre[N],f[N];
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j; for (scanf("%d",&n),f[0]=i=1;i<=n;pre[x]=i++)
	if (scanf("%d",&x),f[i]=f[i-1],pre[x]&&pre[x]!=i-1) inc(f[i],f[pre[x]]);
	return printf("%d",f[n]),0;
}


C - Differ by 1 Bit

我们可以把所有数都\(\operatorname{xor}\)\(A\),这样就是找一条\(0\to A\operatorname{xor} B\)的路径,最后再\(\operatorname{xor}\)回去即可

首先考虑判断无解,我们发现每次操作会改变一位的值,换句话说会改变\(1\)的个数的奇偶性

然后我们一共要做奇数次变换,因此若\(A\operatorname{xor} B\)\(1\)的个数是奇数,那么才合法

接下来考虑如何构造,我们考虑把问题抽象成在一个\(n\)维的超立方体的顶点之间走路,让你找出一条路径能走到目标点并且需要经过所有顶点

很容易想到我们可以先把\(n-1\)维的超立方体走完,然后一步走到\(n\)维的上面去

具体地,我们每次选出一个二进制位\(p\)满足\(A\operatorname{xor} B\)在第\(p\)位上的值是\(1\)

我们先遍历第\(p\)位为\(0\)的所有点组成的\(n-1\)维超立方体后,在去遍历第\(p\)位为\(1\)的所有点组成的\(n-1\)维超立方体

显然在两个\(n-1\)维的超立方体间的连接点是任意取的,因此我们前面的判断是正确的

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=17;
int n,a,b,lim;
inline int count(CI x,int ret=0)
{
	for (RI i=0;i<n;++i) ret+=((x>>i)&1); return ret;
}
inline void DFS(CI x,CI y,CI cs)
{
	if (count(cs)==1) return (void)(printf("%d %d ",y,x^y));
	for (RI i=0,j;i<n;++i) if (((x>>i)&1)&&((cs>>i)&1))
	for (j=0;j<n;++j) if (((cs>>j)&1)&&i!=j)
	return (void)(DFS(1<<j,y,cs^(1<<i)),DFS(x^(1<<i)^(1<<j),y^(1<<i)^(1<<j),cs^(1<<i)));
}
int main()
{
	scanf("%d%d%d",&n,&a,&b); if (count(a^b)&1)
	puts("YES"),DFS(a^b,a,(1<<n)-1); else puts("NO"); return 0;
}


D - A Sequence of Permutations

大力找规律题。我们首先发现关于\(f(p,q)\),设其为\(c\),则:

\[c_{p_i}=q_i\Leftrightarrow c_i=q_{p'_i}\\ s.t. p_x=i\Leftrightarrow p'_i=x \]

考虑我们记\(c_i=q_{p'_i}\)\(c=qp'\),即我们定义其为关于置换群的一种运算

经过实践检验我们发现它满足结合律,但不满足交换律

同时我们发现\((ab')'=ab',(ab'c)'=c'ba'\),因此接下来我们来大力推导找规律

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

由于相邻的\(pp'\)以及\(qq'\)可以约去,因此我们改写一下:

\[\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'\\ &\cdots\end{align} \]

很容易发现若我们令\(T=qp'q'p\),则对于\(n>6\),均有\(a_n=Ta_{n-6}T'\)

证明可以用归纳法,这里略去我才不会说我是找规律出来的

我们发现之前定义的运算和矩阵乘法有着相同的性质,因此我们可以用快速幂处理

总复杂度\(O(n\log k)\)

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k;
struct permutation
{
	int a[N];
	inline permutation(void) { memset(a,0,sizeof(a)); }
	inline int& operator [] (CI x) { return a[x]; }
	inline friend permutation operator ~ (permutation A)
	{
		permutation tp; for (RI i=1;i<=n;++i) tp[A[i]]=i; return tp;
	}
	inline friend permutation operator * (permutation A,permutation B)
	{
		permutation C; for (RI i=1;i<=n;++i) C[i]=A[B[i]]; return C;
	}
	inline friend permutation operator ^ (permutation A,int p)
	{
		permutation t; for (RI i=1;i<=n;++i) t[i]=i;
		for (;p;p>>=1,A=A*A) if (p&1) t=t*A; return t;
	}
	inline void print(void)
	{
		for (RI i=1;i<=n;++i) printf("%d ",a[i]);
	}
}a[10],T;
int main()
{
	RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[1][i]);
	for (i=1;i<=n;++i) scanf("%d",&a[2][i]);
	for (i=3;i<=6;++i) a[i]=a[i-1]*(~a[i-2]);
	int d=k/6,r=k%6; if (!r) --d,r=6; T=a[2]*(~a[1])*(~a[2])*a[1];
	return ((T^d)*a[r]*((~T)^d)).print(),0;
}


E - Snuke the Phantom Thief

很妙的网络流建模题,增长了有用的姿势

首先我们考虑一维的问题怎么做,考虑对限制\((a_i,b_i)\)进行转化

一个比较显然的的结论,若限制\(\le a_i\)的珠宝有\(b_i\)个,则选出的珠宝中第\(b_i+1\)大的一定\(>a_i\)

然后考虑\(\le a_i\)如何转化,直接做不方便,我们考虑枚举选出的珠宝总数\(k\)

那么若限制\(\ge a_i\)的珠宝有\(b_i\)个,则选出的珠宝中第\(k-b_i\)大的一定\(<a_i\)

那么现在我们可以得到\(k\)个二元组\((l_i,r_i)\),表示选出的第\(i\)个珠宝的坐标范围,显然\(l_i,r_i\)均要满足单调性

现在问题其实就是每个二元组可以在它能选择的珠宝中选择一个,每个二元组只能和一个珠宝匹配,很显然就是个最大权匹配问题

那么现在两维的也很简单,我们把\(n\)个点拆成两份,分别表示横纵坐标,与对应的关于横纵坐标的\(k\)个点连边,同时\(n\)个点连\((i,i')\)的边表示这个珠宝只能选一次

直接跑最大费用最大流即可,总复杂度\(费用流O(n\times \text{费用流})\)

#include<cstdio>
#include<cctype>
#include<iostream>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
const int N=350,M=(N*N<<1)+(3*N);
const long long INF=1e18;
struct edge
{
	int to,nxt,v; long long c;
}e[M<<1]; char opt[N]; long long ans,v[N];
int n,m,head[N],cnt,x[N],y[N],L[N],R[N],U[N],D[N],a[N],b[N],s,t;
inline char getch(void)
{
	char ch; while (!isalpha(ch=getchar())); return ch;
}
inline void addedge(CI x,CI y,CI v,const long long& c)
{
	e[++cnt]=(edge){y,head[x],v,c}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y],0,-c}; head[y]=cnt;
}
#define to e[i].to
namespace NF //Network_Flow
{
	queue <int> q; int pre[N],lst[N],cap[N]; long long dis[N]; bool vis[N];
	inline bool SPFA(CI s,CI t)
	{
		RI i; for (i=s;i<=t;++i) dis[i]=-INF,cap[i]=1e9,pre[i]=-1;
		pre[s]=0; q.push(s); dis[s]=0; vis[s]=1; while (!q.empty())
		{
			int now=q.front(); q.pop(); vis[now]=0;
			for (i=head[now];i;i=e[i].nxt)
			if (e[i].v&&dis[to]<dis[now]+e[i].c)
			{
				dis[to]=dis[now]+e[i].c; pre[to]=now; lst[to]=i;
				cap[to]=min(cap[now],e[i].v); if (!vis[to]) vis[to]=1,q.push(to);
			}
		}
		return ~pre[t];
	}
	inline long long MCMF(CI s,CI t)
	{
		long long ret=0; while (SPFA(s,t))
		{
			ret+=dis[t]*cap[t]; for (int nw=t;nw;nw=pre[nw])
			e[lst[nw]].v-=cap[t],e[lst[nw]^1].v+=cap[t];
		}
		return ret;
	}
	inline void clear(CI n)
	{
		cnt=1; for (RI i=0;i<=n;++i) head[i]=0;
	}
};
#undef to
inline long long calc(CI k)
{
	RI i,j; for (i=1;i<=k;++i) L[i]=D[i]=0,R[i]=U[i]=1e9;
	for (NF::clear(2*n+2*k+1),i=1;i<=m;++i) if (b[i]<k) 
	{
		if (opt[i]=='L') L[b[i]+1]=a[i]+1;
		if (opt[i]=='R') R[k-b[i]]=a[i]-1; 
		if (opt[i]=='D') D[b[i]+1]=a[i]+1;
		if (opt[i]=='U') U[k-b[i]]=a[i]-1; 
	}
	for (i=2;i<=k;++i) L[i]=max(L[i],L[i-1]),D[i]=max(D[i],D[i-1]);
	for (i=k-1;i;--i) R[i]=min(R[i],R[i+1]),U[i]=min(U[i],U[i+1]);
	for (s=0,t=2*n+2*k+1,i=1;i<=k;++i) addedge(s,i,1,0),addedge(k+2*n+i,t,1,0);
	for (i=1;i<=n;++i) addedge(k+i,k+n+i,1,v[i]);
	for (i=1;i<=k;++i) for (j=1;j<=n;++j)
	{
		if (L[i]<=x[j]&&x[j]<=R[i]) addedge(i,k+j,1,0);
		if (D[i]<=y[j]&&y[j]<=U[i]) addedge(k+n+j,k+2*n+i,1,0);
	}
	return NF::MCMF(s,t);
}
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%lld",&x[i],&y[i],&v[i]);
	for (scanf("%d",&m),i=1;i<=m;++i) opt[i]=getch(),scanf("%d%d",&a[i],&b[i]);
	for (i=1;i<=n;++i) ans=max(ans,calc(i)); return printf("%lld",ans),0;
}


Postscript

暑假要结束了,赶紧冲冲冲一波!

posted @ 2020-08-20 16:13  空気力学の詩  阅读(118)  评论(0编辑  收藏  举报