Atcoder agc041简要题解

A - Table Tennis Training

B - Voting Judges

C - Domino Quality

N ≤ 3 N\leq 3 N3时特判。
经过一些尝试,可以在 N = 4 , 5 , 6 , 7 N=4,5,6,7 N=4,5,6,7时都构造出每行每列恰有 3 3 3个的方案,具体构造的方案可以看代码。注意到在 N N N的一个每行每列恰有 3 3 3个的方案右下方放一个 N = 4 N=4 N=4的方案,就可以得到一个 N + 4 N+4 N+4的每行每列恰有 3 3 3个的方案,于是可以对 N ≥ 4 N\geq 4 N4的所有 N N N构造出每行每列恰有 3 3 3个的方案。
时间复杂度 O ( N 2 ) \mathcal O(N^2) O(N2)

#include 

using namespace std;

typedef long long ll;

const char* sol3[3]={"aab","c.b","cdd"};
const char* sol4[4]={"aacd","bbcd","efgg","efhh"};
const char* sol5[5]={"aabbc","dee.c","d..fg","h..fg","hiijj"};
const char* sol6[6]={"aabbc.","ddeec.",".f.g.h",".f.g.h","i.j.kk","i.j.ll"};
const char* sol7[7]={"aa.bb.c",".dd..ec","f.gg.e.","f...h.i",".j..h.i","kj..ll.","k.mm.nn"};

char ans[1005][1005];

int main() {
  int n;
  scanf("%d",&n);
  if (n<=2) {
  	puts("-1");
  	return 0;
  }
  if (n==3) {
  	for(int i=0;i<3;i++) puts(sol3[i]);
  	return 0;
  }
  for(int i=0;i<n;i++)
    for(int j=0;j<n;j++) ans[i][j]='.';
  int d=0;
  while (n-d>=8) {
  	for(int i=0;i<4;i++)
  	  for(int j=0;j<4;j++) ans[d+i][d+j]=sol4[i][j];
  	d+=4;
  }
  if (n-d==4) {
  	for(int i=0;i<4;i++)
  	  for(int j=0;j<4;j++) ans[d+i][d+j]=sol4[i][j];
  }
  else if (n-d==5) {
  	for(int i=0;i<5;i++)
  	  for(int j=0;j<5;j++) ans[d+i][d+j]=sol5[i][j];
  }
  else if (n-d==6) {
  	for(int i=0;i<6;i++)
  	  for(int j=0;j<6;j++) ans[d+i][d+j]=sol6[i][j];
  }
  else {
  	for(int i=0;i<7;i++)
  	  for(int j=0;j<7;j++) ans[d+i][d+j]=sol7[i][j];
  }
  for(int i=0;i<n;i++) puts(ans[i]);
  return 0;
} 

D - Problem Scores

显然只用考虑 K = ⌊ N − 1 2 ⌋ K=\lfloor \frac{N-1}{2}\rfloor K=2N1即可。
假设 N N N是偶数(奇数类似),那么条件等价于 ∑ i = 1 K ( A N − K + i − A N − K − i ) < A 1 \sum_{i=1}^{K}(A_{N-K+i}-A_{N-K-i})< A_1 i=1K(ANK+iANKi)<A1
考虑DP,设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示考虑到 i i i,确定了 N − K − i N-K-i NKi N − K + i N-K+i NK+i之间的相对顺序, A N − K + i − A N − K − i = j A_{N-K+i}-A_{N-K-i}=j ANK+iANKi=j ∑ l = 1 i ( A N − K + l − A N − K − l ) = k \sum_{l=1}^{i}(A_{N-K+l}-A_{N-K-l})=k l=1i(ANK+lANKl)=k的方案数,转移的时候可以用前缀和加速。注意到 j ⋅ ( K − i + 1 ) ≤ N j\cdot(K-i+1)\leq N j(Ki+1)N,于是根据调和级数有用的状态数目是 O ( N 2 log ⁡ N ) \mathcal O(N^2\log N) O(N2logN)的。
时间复杂度 O ( N 2 log ⁡ N ) \mathcal O(N^2\log N) O(N2logN),也存在 O ( N 2 ) \mathcal O(N^2) O(N2)的DP。

#include 

using namespace std;

typedef long long ll; 

int MOD;

inline void add(int &x,int y) {
  ((x+=y)>=MOD)?x-=MOD:0;
}

int f[2][5005][5005],sum1[5005],sum2[5005];

inline ll S(int n) {
  return n*(n+1)>>1;
}

int main() {
  int n=5000; 
  scanf("%d%d",&n,&MOD);
  if (n==2) {
  	puts("3");
  	return 0;
  }
  int k=((n-1)>>1);
  int cur=0;
  for(int i=0;i*k<=n;i++) f[cur][i][i]=((n&1)?1:i+1);
  for(int i=2;i<=k;i++) {
  	cur^=1;
  	memset(sum1,0,sizeof(sum1));
  	memset(sum2,0,sizeof(sum2));
  	for(int j=0;j*(k-i+1)<=n;j++) {
  		memset(f[cur][j],0,sizeof(f[cur][j]));
  		for(int l=0;l<=n;l++) {
  			sum1[l]=(sum1[l]+f[cur^1][j][l])%MOD;
  			sum2[l]=(sum2[l]+sum1[l])%MOD;
		  }
		for(int l=j;l<=n;l++) f[cur][j][l]=sum2[l-j];
	  }
  }
  int ans=0;
  for(int i=0;i<=n;i++)
    for(int j=0;j<=i;j++)
      if (f[cur][j][i]&&i+j<n) ans=(ans+f[cur][j][i]*S(n-j-i))%MOD;
  printf("%d\n",ans);
  return 0;
} 

E - Balancing Network

T=1

如果我们枚举 w w w,显然可以从右往左贪心确定方向,来尽可能让从所有电线出发走到 w w w。具体地,令 p i p_i pi为一个 01 01 01变量, p i = 1 p_i=1 pi=1当且仅当当前从 i i i出发能走到 w w w。一开始只有 p w = 1 p_w=1 pw=1,现在在最左边加入一个平衡器 ( u , v ) (u,v) (u,v),只用在 p u ≠ p v p_u\neq p_v pu=pv的时候从 0 0 0的那条指向 1 1 1的那条即可(这样能让 p u = p v = 1 p_u=p_v=1 pu=pv=1)。最后合法当且仅当 ∀ 1 ≤ i ≤ N \forall 1\leq i\leq N 1iN p i = 1 p_i=1 pi=1
现在的问题是不能枚举 w w w。换个角度,如果我们能够找出一个可能的 w w w或判断不存在的话,就可以用上面的算法构造了。简单归纳可以证明一个结论, w w w可能可行当且仅当 ∀ 1 ≤ i ≤ N \forall 1\leq i\leq N 1iN,均存在一个方案使得 i i i最后可以走到 w w w
那么就可以做一个DP,设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示走完后 i i i个平衡器后 j j j能否走到 k k k,在最左边加入一个平衡器 ( u , v ) (u,v) (u,v)转移为 ∀ 1 ≤ k ≤ N \forall 1\leq k\leq N 1kN F [ i − 1 ] [ u ] [ k ] = F [ i − 1 ] [ v ] [ k ] = F [ i ] [ u ] [ k ] ∨ F [ i ] [ v ] [ k ] F[i-1][u][k]=F[i-1][v][k]=F[i][u][k]\lor F[i][v][k] F[i1][u][k]=F[i1][v][k]=F[i][u][k]F[i][v][k]。最后只需看一下是否存在一个 w w w使得 ∀ 1 ≤ i ≤ N \forall 1\leq i\leq N 1iN F [ M ] [ i ] [ w ] = 1 F[M][i][w]=1 F[M][i][w]=1
这个DP显然可以用bitset加速,时间复杂度 O ( N M w ) \mathcal O(\frac{NM}{w}) O(wNM)

T=2

代码里的是我自己的 O ( M M ) \mathcal O(M\sqrt M) O(MM )做法。下面说一下题解的做法。
N = 2 N=2 N=2时一定无解,事实上,可以证明当 N ≤ 3 N\leq 3 N3时一定有解。
注意到若证明了 N = 3 N=3 N=3一定有解,那么 N > 3 N>3 N>3的情况也一定有解(只需对于 ≤ 3 \leq 3 3的电线用 N = 3 N=3 N=3的构造,再令所有 ( u , v ) (u,v) (u,v) u ≤ 3 , v > 3 u\leq 3,v>3 u3,v>3)的平衡器由 v v v指向 u u u即可)。而 N = 3 N=3 N=3的时候,考虑从左往右构造,任意时刻维护两个划分集合,一个大小为 1 1 1,一个大小为 2 2 2的,使得两个集合走到的电缆不相同。这样考虑在最右边加入一个平衡器 ( u , v ) (u,v) (u,v),若 ( u , v ) (u,v) (u,v)均在大小为 2 2 2的集合随意定向,否则不妨设 u u u在大小为 2 2 2的集合里,那么定向为 u u u指向 v v v,就能保证还是划分为两个集合。
时间复杂度 O ( M ) \mathcal O(M) O(M)。Codechef February Challenge 2020的Balancing Network Revisited是这题的加强版。

#include 
#define FR first
#define SE second
#define last last2

using namespace std;

typedef bitset<50001> bs;
typedef pair<int,int> pr;

pr e[100005];

namespace Solve1 {

bs f[50005];

int dp(int n,int m) {
  for(int i=1;i<=n;i++) f[i].flip(i);
  for(int i=m;i>0;i--) {
  	int u=e[i].FR,v=e[i].SE;
  	f[u]|=f[v];
  	f[v]|=f[u];
  }
  bs cur;
  cur.set();
  for(int i=1;i<=n;i++) cur&=f[i];
  if (cur.any()) return cur._Find_first();
  else return 0;
}

bool ans[100005],vis[50005];

void solve(int n,int m) {
  int x=dp(n,m);
  if (!x) {
  	puts("-1");
  	return;
  }
  vis[x]=1;
  for(int i=m;i>0;i--) {
  	int u=e[i].FR,v=e[i].SE;
  	if (vis[u]&&!vis[v]) {
  		vis[v]=1;
  		ans[i]=1;
	  }
	else if (vis[v]&&!vis[u]) {
		vis[u]=1;
		ans[i]=0;
	}
  }
  for(int i=1;i<=m;i++) putchar((ans[i])?'^':'v');
  printf("\n");
}

}

namespace Solve2 {

map <pr,bool> mp;

bool f[505][505];
vector <pr> trans[505][505];

void output1(int x,int y,int m) {
  for(int i=1;i<=m;i++)
    if (e[i].FR==x||e[i].FR==y) putchar('^');
    else putchar('v');
  printf("\n");
} 

int r[505][505];
bool ans[100005];

void output2(int x,int y,int n,int m) {
  for(int i=1;i<n;i++)
    for(int j=i+1;j<=n;j++) r[i][j]=(int)trans[i][j].size()-1;
  int last=0;
  for(;;) {
  	while (r[x][y]>=0&&trans[x][y][r[x][y]].FR<=last) r[x][y]--;
  	if (r[x][y]<0) break;
  	pr t=trans[x][y][r[x][y]--];
  	for(int i=last+1;i<t.FR;i++)
  	  if (e[i].FR==x||e[i].FR==y) ans[i]=1;
  	  else ans[i]=0;
  	last=t.FR;
  	int u=e[t.FR].FR,v=e[t.FR].SE;
  	if (t.SE) {
  		if (x==u) x=v; else y=v;
  		ans[t.FR]=0;
	  } 
	else {
		if (x==v) x=u; else y=u;
		ans[t.FR]=1;
	}
	if (x>y) swap(x,y);
  }
  for(int i=last+1;i<=m;i++)
    if (e[i].FR==x||e[i].FR==y) ans[i]=1;
    else ans[i]=0;
  for(int i=1;i<=m;i++) putchar((ans[i])?'^':'v');
  printf("\n");
}

pr dp(int n,int m) {
  for(int i=1;i<n;i++)
    for(int j=i+1;j<=n;j++) f[i][j]=1;
  for(int i=m;i>0;i--) {
  	int u=e[i].FR,v=e[i].SE;
    f[u][v]=0;
    for(int j=1;j<u;j++)
      if (!f[j][u]&&f[j][v]) {
      	f[j][u]=1;
      	trans[j][u].push_back(pr(i,1));
	  }
	  else if (!f[j][v]&&f[j][u]) {
	  	f[j][v]=1;
	  	trans[j][v].push_back(pr(i,0));
	  }
    for(int j=u+1;j<v;j++)
      if (!f[u][j]&&f[j][v]) {
      	f[u][j]=1;
      	trans[u][j].push_back(pr(i,1));
	  }
	  else if (!f[j][v]&&f[u][j]) {
	  	f[j][v]=1;
	  	trans[j][v].push_back(pr(i,0));
	  }
    for(int j=v+1;j<=n;j++)
      if (!f[u][j]&&f[v][j]) {
      	f[u][j]=1;
      	trans[u][j].push_back(pr(i,1));
	  }
	  else if (!f[v][j]&&f[u][j]) {
	  	f[v][j]=1;
	  	trans[v][j].push_back(pr(i,0));
	  }
  }
  for(int i=1;i<n;i++)
    for(int j=i+1;j<=n;j++)
      if (f[i][j]) return pr(i,j);
  return pr(-1,-1);
}

void solve(int n,int m) {
  for(int i=1;i<=m;i++) mp[e[i]]=1;
  for(int i=1;i<n;i++)
    for(int j=i+1;j<=n;j++)
      if (!mp.count(pr(i,j))) {
      	output1(i,j,m);
      	return;
	  }
  pr t=dp(n,m);
  if (t.FR==-1) {
  	puts("-1");
  	return;
  }
  output2(t.FR,t.SE,n,m);
}

}

int main() {
  int n,m,T;
  scanf("%d%d%d",&n,&m,&T);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	e[i]=pr(x,y);
  }
  if (T==1) Solve1::solve(n,m);
  else Solve2::solve(n,m);
  return 0;
}

F - Histogram Rooks

显然考虑分层从上往下DP,同一层不连通的部分分开DP。
容易想到设 f [ ( d , l , r ) ] [ i ] [ j ] f[(d,l,r)][i][j] f[(d,l,r)][i][j]表示考虑第 d d d行往上, l l l列到 r r r列之间的部分( l l l列到 r r r列是第 d d d层的极长连续段),其中有 i i i列已经放过车了,有 j j j列不仅没放,且因为在 d d d行以上某一行没放车,所以必须在 d d d行往下的该列放车的方案数。转移的时候考虑加入 d − 1 d-1 d1行,那么首先要做背包合并 d d d行的极长连续段,然后再根据第 d d d行的放法更新状态。可惜转移时间复杂度太高,不能通过。
考虑优化DP的状态,注意到我们的状态相当于把列分成了三类,其中一类是 d d d行往下可以不用放车(原来状态的 i i i),一类是 d d d行往下必须放车(原来状态的 j j j),剩下的是还不确定的(现在这些列还没有放车,有可能以后因为某行全部没放导致必须在更下面放)。我们发现如果我们钦定 d d d行往下是否存在某行全没放的话,就可以把第三类当作第一类或第二类来处理了。具体的,设 F [ ( d , l , r ) ] [ i ] F[(d,l,r)][i] F[(d,l,r)][i]表示考虑第 d d d行往上, l l l列到 r r r列之间的部分( l l l列到 r r r列是第 d d d层的极长连续段),其中一三类总共有 i i i列,且钦定 d d d行往下每行都必须放的方案数; G [ ( d , l , r ) ] [ i ] G[(d,l,r)][i] G[(d,l,r)][i]表示其中一类总共有 i i i列,且钦定 d d d行往下存在某行一个没放的方案数,答案即为 F [ ( 1 , 1 , N ) ] [ N ] F[(1,1,N)][N] F[(1,1,N)][N]。转移的时候以 F F F为例,它只会转移到 F F F,仍然考虑加入 d − 1 d-1 d1行,首先做背包合并 d d d行的极长连续段,然后考虑枚举有多少二类列在这一行放车变为一类列,这部分贡献是组合数,而对于一三类列来说,它们可放可不放,贡献为 2 i 2^i 2i,注意这里需要保证不能一个车都不放。 G G G的转移也类似,但是需要注意 G G G可能转移到 F F F G G G
简单分析一下可以知道时间复杂度为 O ( N 3 ) \mathcal O(N^3) O(N3)

#include 
#define MOD 998244353 
#define last last2

using namespace std;

typedef long long ll;

ll C[405][405],pow2[405];

void pre(int n) {
  for(int i=0;i<=n;i++) C[i][0]=1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
  pow2[0]=1;
  for(int i=1;i<=n;i++) pow2[i]=pow2[i-1]*2LL%MOD;
}

int num[405];
int f[405][405],g[405][405];

void dp(int d,int l,int r) {
  static int sum1[405],sum2[405];
  memset(f[d],0,sizeof(f[d]));
  memset(g[d],0,sizeof(g[d]));
  f[d][0]=g[d][0]=1;
  int last=l-1,s=0;
  for(int i=l;i<=r+1;i++)
    if (i>r||num[i]==d) {
    	if (last+1<i) {
    		dp(d+1,last+1,i-1);
    		int k=i-last-1;
    		memset(sum1,0,sizeof(sum1));
    		for(int j=0;j<=s;j++)
    		  for(int l=0;l<=k;l++) sum1[j+l]=(sum1[j+l]+(ll)f[d][j]*f[d+1][l])%MOD;
    		for(int j=0;j<=s+k;j++) f[d][j]=sum1[j];
    		memset(sum2,0,sizeof(sum2));
    		for(int j=0;j<=s;j++)
    		  for(int l=0;l<=k;l++) sum2[j+l]=(sum2[j+l]+(ll)g[d][j]*g[d+1][l])%MOD;
    		for(int j=0;j<=s+k;j++) g[d][j]=sum2[j];
    		s+=k;
		}
		last=i;
	}
  int len=r-l+1,t=len-s;
  memset(sum1,0,sizeof(sum1));
  memset(sum2,0,sizeof(sum2));
  for(int i=0;i<=s;i++) {
  	int t2=s-i;
    for(int j=0;j<=t2;j++)
	  sum1[i+t+j]=(sum1[i+t+j]+C[t2][j]*((j)?pow2[i+t]:pow2[i+t]-1)%MOD*f[d][i])%MOD;
	t2=len-i;
	for(int j=0;j<=t2;j++)
	  sum2[i+j]=(sum2[i+j]+C[t2][j]*((j)?pow2[i]:pow2[i]-1)%MOD*g[d][i])%MOD;
	sum1[i]=(sum1[i]+g[d][i])%MOD;
	sum2[i]=(sum2[i]+g[d][i])%MOD;
  }
  for(int i=0;i<=len;i++) {
  	f[d][i]=sum1[i];
  	g[d][i]=sum2[i];
  }
}

int main() {
  int n;
  scanf("%d",&n);
  pre(n);
  for(int i=1;i<=n;i++) scanf("%d",&num[i]);
  dp(1,1,n);
  printf("%d\n",f[1][n]);
  return 0;
}

你可能感兴趣的:(atcoder,构造,动态规划)