略
略
当 N ≤ 3 N\leq 3 N≤3时特判。
经过一些尝试,可以在 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 N≥4的所有 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;
}
显然只用考虑 K = ⌊ N − 1 2 ⌋ K=\lfloor \frac{N-1}{2}\rfloor K=⌊2N−1⌋即可。
假设 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(AN−K+i−AN−K−i)<A1。
考虑DP,设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示考虑到 i i i,确定了 N − K − i N-K-i N−K−i到 N − K + i N-K+i N−K+i之间的相对顺序, A N − K + i − A N − K − i = j A_{N-K+i}-A_{N-K-i}=j AN−K+i−AN−K−i=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(AN−K+l−AN−K−l)=k的方案数,转移的时候可以用前缀和加速。注意到 j ⋅ ( K − i + 1 ) ≤ N j\cdot(K-i+1)\leq N j⋅(K−i+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;
}
如果我们枚举 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 ∀1≤i≤N, 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 ∀1≤i≤N,均存在一个方案使得 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 ∀1≤k≤N, 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[i−1][u][k]=F[i−1][v][k]=F[i][u][k]∨F[i][v][k]。最后只需看一下是否存在一个 w w w使得 ∀ 1 ≤ i ≤ N \forall 1\leq i\leq N ∀1≤i≤N, 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)。
代码里的是我自己的 O ( M M ) \mathcal O(M\sqrt M) O(MM)做法。下面说一下题解的做法。
当 N = 2 N=2 N=2时一定无解,事实上,可以证明当 N ≤ 3 N\leq 3 N≤3时一定有解。
注意到若证明了 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 u≤3,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;
}
显然考虑分层从上往下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 d−1行,那么首先要做背包合并 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 d−1行,首先做背包合并 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;
}