将 i i i向 a [ i ] a[i] a[i]连边,那么我们得到一个内向基环树森林(环上点没有外向边)。
显然环外的骰子直接分配极大值就行了。
对于环内的骰子我们有这样一种策略:选择一个点,向父亲方向依次放下1~n的值,再选择它的父亲,向父亲方向依次放下n+1 ~ 2n。如此重复直到填满。
可以证明这样的策略填出来的环内概率 ≥ 1 2 \ge\frac{1}2 ≥21,而且只有当环长为 3 3 3,骰子面数为 4 4 4的时候会出现 1 2 \frac{1}2 21,按照BZOJ上样例来特判就行了。
代码:
#include<bits/stdc++.h> #define ll long long #define re register #define gc getchar #define cs const namespace IO{ template<typename T> inline T get(){ re char c; while(!isdigit(c=gc()));re T num=c^48; while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48); return num; } inline int getint(){return get<int>();} } using namespace IO; using std::cerr; using std::cout; cs int N=202; cs int sp[4][5]={ {}, {0,1,3,10,11}, {0,2,7,8,9}, {0,4,5,6,12}, }; int n,m; int a[N]; int ans[N][N]; int q[N],qn; int d[N]; bool vis[N]; inline void top_sort(){ int num=n*m; for(int re i=1;i<=n;++i)if(!d[i])q[++qn]=i; for(int re i=1,u;i<=qn;++i){ vis[u=q[i]]=true; for(int re i=1;i<=m;++i)ans[u][i]=num--; if(!--d[a[u]])q[++qn]=a[u]; } for(int re i=1,j;i<=n;++i)if(!vis[i]){ for(j=i,qn=0;!vis[j];j=a[j])vis[q[++qn]=j]=true; if(qn<=2)puts("0"),exit(0); num-=qn*m; if(qn==3&&m==4){ ans[q[1]][1]=sp[1][1]+num; ans[q[1]][2]=sp[1][2]+num; ans[q[1]][3]=sp[1][3]+num; ans[q[1]][4]=sp[1][4]+num; ans[q[2]][1]=sp[2][1]+num; ans[q[2]][2]=sp[2][2]+num; ans[q[2]][3]=sp[2][3]+num; ans[q[2]][4]=sp[2][4]+num; ans[q[3]][1]=sp[3][1]+num; ans[q[3]][2]=sp[3][2]+num; ans[q[3]][3]=sp[3][3]+num; ans[q[3]][4]=sp[3][4]+num; } else { for(int re cnt=1,i=1;cnt<=m;i=i==1?qn:i-1,++cnt){ ans[q[i]][cnt]=++num; for(int re j=i==1?qn:i-1;j^i;j=j==1?qn:j-1) ans[q[j]][cnt]=++num; } num-=qn*m; } } } signed main(){ n=getint(),m=getint(); for(int re i=1;i<=n;++i){ ++d[a[i]=getint()]; if(a[i]==i)puts("0"),exit(0); } top_sort(); for(int re i=1;i<=n;++i) for(int re j=1;j<=m;++j) cout<<ans[i][j]<<(j==m?'\n':' '); return 0; }