A. Reverse

手玩第二个样例,发现可以dp。

但是似乎没有显然的拓扑序,所以直接跑最短路。

然而暴力建图$n^2$,解决方法是线段树优化建图。

利用$bfs$每个点只在第一次被更新时最优的性质,直接用$set$或者链表维护也是可以的。

 

 

 

B. Silhouette

首先是一个显然的性质:

因为不关注先后左右的相对位置,将$a$ $b$数组任意交换顺序,不会对答案造成影响。

那么不妨将$a$,$b$分别排序。

对于矩阵上每一个点$(i,j)$,设它的值为$min(a[i],b[j])$

从大到小考虑每一个值,那么这个值必定形成矩形或一个$L$形。

矩形可以当作没有棱角的$L$形考虑,只要考虑$L$形时的方案数就可以了。

设矩形为$a$行$b$列,上面还有$c$行,右面还有$d$列。

容易发现,如果上面有c行,那么这c行一定已经合法。

如果右面有d列,那么这d列已经合法,所以无需进行容斥,直接统计答案就可以了。

我们只要使a行b列的矩形合法。设$min(a[i],b[j])=x$。

合法的条件是:每一行每一列存在$x$且不存在$>x$的值。

设$f(i)$表示保证每一列合法,有$i$行不合法的方案数。

$ans=\sum \limits_{i=0}^{a}\binom{a}{i}(-1)^if(i)$

$f(i)=(x^i*((x+1)^{a+c-i}-x^{a+c-i}))^b*(x^i*(x+1)^{c-i})^d$

因为互不影响,将所有的ans累乘是最终答案。

 

 

 

C. Seat

很扯的一道题,下面是自己注释的std。

很多东西搞的不是很懂,但是可以强行解释。

 1 //注意
 2 //本题中odd为偶数,even为奇数
 3 #include<bits/stdc++.h>
 4 using namespace std;
 5 const int N=1030;
 6 int n,mod;
 7 int qpow(int x,int k,int ans=1){
 8     while(k){
 9         if(k&1) ans=ans*x%mod;
10         x=x*x%mod;
11         k>>=1;
12     }
13     return ans;
14 }
15 //dp为答案
16 //f为题解中dp数组
17 int dp[N][N],f[N][N],g[N][N],vis[N],inv[N],cnt[N],odd[N],pos[N];
18 int main(){
19     scanf("%d%d",&n,&mod);
20     for(int i=1;i<=n;++i) inv[i]=qpow(i,mod-2);
21     vis[0]=vis[n+1]=true;
22     for(int i=1;i<=n;++i){
23         int pl=0,pr=0,mx;
24         for(int j=0;j<=n;++j){
25             int r=j+1;
26             while(!vis[r]) ++r;
27             if(r-j>pr-pl) pl=j,pr=r;
28             j=r-1;
29         }
30         ++cnt[mx=pr-pl>>1]; odd[mx]+=pr-pl&1;
31         pos[i]=pl+mx; vis[pl+mx]=true;
32     }
33     int sum=n;
34     for(int i=1;i<=n;++i){
35         if(!cnt[i]) continue;
36         int l=sum-cnt[i]+1,r=sum;
37         if(i==1) for(int j=l;j<=r;++j) for(int k=l;k<=r;++k) dp[j][pos[k]]=inv[cnt[i]];//直接赋值为距离为1个数的逆元? 或许这里也被平均掉了
38         else{
39             for(int j=0;j<=cnt[i];++j) for(int k=0;k<=odd[i];++k) f[j][k]=0;//清空数组
40             f[0][odd[i]]=1; //还没有坐人,那么该层剩下odd[i]个偶数区间的概率为1
41             int p=l+odd[i]-1; //偶数区间长度的最后一个人
42             for(int j=1;j<=cnt[i];++j){
43                 int oddw=0,evenw=0;//odd为偶数 even为奇数
44                 for(int k=0;k<=odd[i];++k){
45                     if(!f[j-1][k]) continue;//dp转移 所以f值为0的时候可以剪枝 k表示剩余多少个偶数区间
46                     int frac=(cnt[i]-(j-1))+k,w=0; //括号内表示剩余的区间个数 +k表示剩余多少个转移点
47                     if(k){//还有偶区间剩余
48                         w=f[j-1][k]*k*2%mod*inv[frac]%mod;//占一个偶区间位置 那么概率为k*2/转移点
49                         oddw=(oddw+w*inv[odd[i]*2])%mod; //方便累加答案 对于这一次的转移 可能作用在不同的转移点
50                         (f[j][k-1]+=w)%=mod;
51                     }
52                     if(cnt[i]-odd[i]){//可以向奇区间转移
53                         w=f[j-1][k]*(frac-2*k)%mod*inv[frac]%mod;//向奇数区间转移的概率
54                         evenw=(evenw+w*inv[(cnt[i]+odd[i])-odd[i]*2])%mod;//向不同奇数区间转移
55                         (f[j][k]+=w)%=mod;
56                     }
57                 }
58                 for(int u=l;u<=p;++u) (dp[l+j-1][pos[u]]+=oddw)%=mod,(dp[l+j-1][pos[u]+1]+=oddw)%=mod;//累加答案
59                 for(int u=p+1;u<=r;++u) (dp[l+j-1][pos[u]]+=evenw)%=mod;//这里存在疑问 似乎将偶数区间默认加给了前面的pos
60             }
61             for(int j=l;j<=p;++j){//
62                 int L=pos[j]-i+1,R=pos[j]+i;//当前的偶区间左右端点
63                 for(int v=L;v<=R;++v){
64                     if(v==pos[j]) continue;//不能是选择的点
65                     for(int u=r+1;u<=n;++u){//后面每一个人
66                         int s=v<pos[j]?v+i+1:v-i,w=dp[u][v]*inv[2]%mod;//后一个人在位置v的概率除2
67                         (g[u][v]+=w)%=mod; (g[u][s]+=w)%=mod;//平均一下 虽然不知道为啥 但是感觉很对
68                     }
69                 }
70                 for(int v=L;v<=R;++v) for(int u=r+1;u<=n;++u) dp[u][v]=g[u][v],g[u][v]=0;
71             }
72         }
73         sum-=cnt[i];//考虑下一层 剩余人数减少
74     }
75     for(int i=1;i<=n;i++,puts("")) for(int j=1;j<=n;j++) printf("%d ",dp[i][j]);
76     return 0;
77 }