群论
置换
cow sorting
题意: 有N头牛,将他们的特征值排升序,每交换两头牛,代价就是两头牛的和,求最小的代价和。
思路: 赤裸裸的置换群,简单学习后,直接上手。。。记录下原数和位置,排序后,找到循环节(原数列和升序后的数列中相应多个位置的出现的数的集合完全相等即为一个循环节),个数为ci,记下这个循环节中的除第一个元素的所有的和ans,然后就是分两种情况:1)将循环节中的元素互换,即将这个循环节中的最小元素与其他元素互换,需要的代价为(ans+(ci-1)*循环节内最小元素);2)将整个数列的最小值和循环节中的最小值互换后,用整个数列的最小值进行互换,再将原数换回来,代价为(ans+(ci-1)*整个数列的最小元素+2*(整个数列的最小元素+循环节内的最小元素))。取这两个当中的较小值加到sum上,最后输出sum就是答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct use{ int num,p; }a[100001]; int my_comp(const use &x,const use &y) { if (x.num<y.num) return 1; else return 0; } int main() { int i,j,n,d,t,minn,mini,next,ci; long long sum; scanf("%d",&n); sum=0; memset(a,0,sizeof(a)); minn=2100000000; for (i=1;i<=n;++i) { scanf("%d",&a[i].num); a[i].p=i; if (a[i].num<minn) minn=a[i].num; } sort(a+1,a+n+1,my_comp); for (i=1;i<=n;++i) { if (a[i].p!=-1) { ci=1; d=a[i].p; mini=a[i].num; a[i].num=-1; while (d!=i) { sum+=a[d].num; next=a[d].p; a[d].p=-1; d=next; ++ci; } sum+=min((ci-1)*mini,(ci-1)*minn+2*(minn+mini)); } } printf("%d\n",sum); }
hihocoder MX Loves Game(!!!)
题目大意:给定两个矩阵,每次可以交换整行或整列,求最少交换几次能从一个得到另一个。
思路:因为n比较小,我们可以穷举a1,1=bx,y,然后想办法把a的第一行第一列和b的第x行第y列完全一样,然后判断剩下的(n-1)^2是否相等就可以更新答案了。把行列搞成一样的话可以用置换的思想,sigma循环节长度-1就是交换的次数了。注意数组的下标。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 505 #define inf 2100000000 using namespace std; int ai[maxm][maxm],bi[maxm][maxm],ci[maxm][maxm],di[maxm][maxm],ans,n,pi[maxm]; bool visit[maxm]; bool judge(){ int i,j; for (i=1;i<=n;++i) for (j=1;j<=n;++j) if (ci[i][j]!=ai[i][j]) return false; return true; } void calc(int x,int y){ int i,j,cnt,k; if (ai[1][1]!=bi[x][y]) return; for (i=1;i<=n;++i) for (j=1;j<=n;++j) ci[i][j]=bi[i][j]; for (i=1;i<=n;++i) pi[ci[x][i]]=i; memset(visit,false,sizeof(visit)); for (cnt=n,i=1;i<=n;++i){ if (visit[i]) continue;--cnt; for (j=i;!visit[j];j=pi[ai[1][j]]){ visit[j]=true; for (k=1;k<=n;++k) di[k][j]=ci[k][pi[ai[1][j]]]; } }for (i=1;i<=n;++i) for (j=1;j<=n;++j) ci[i][j]=di[i][j]; for (i=1;i<=n;++i) pi[ci[i][1]]=i; memset(visit,false,sizeof(visit)); for (cnt+=n,i=1;i<=n;++i){ if (visit[i]) continue;--cnt; for (j=i;!visit[j];j=pi[ai[j][1]]){ visit[j]=true; for (k=1;k<=n;++k) di[j][k]=ci[pi[ai[j][1]]][k]; } }for (i=1;i<=n;++i) for (j=1;j<=n;++j) ci[i][j]=di[i][j]; if (judge()) ans=min(ans,cnt); } int main(){ int i,j;scanf("%d",&n); for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&ai[i][j]); for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&bi[i][j]); for (ans=inf,i=1;i<=n;++i) for (j=1;j<=n;++j) calc(i,j); if (ans==inf) printf("-1\n"); else printf("%d\n",ans); }
Burnside引理
bzoj1004 cards
题目大意:要求给n个牌染成sr、sb、sg张红、蓝、绿色,给定m种置换(置换后的颜色排列算作同种方案),同时多个置换可以由一个代替,求不同的染色数。
思路:ans=1/|G|*sigma(g∈G)Mg(Mg表示在g这个置换下不变的元素个数)。这道题目中求出Mg(经过这个置换后染色方案不变的方案数),就是相当于对于一个置换中的循环节都染成相同颜色,最后染成sr、sb、sg个的方案数,可以dp一下。因为模质数,所以除可以用逆元。注意还有一个置换((1,2,...,n)(1,2,...,n))也要计算进答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 65 using namespace std; int fi[maxm][maxm][maxm]={0},po[maxm],sr,sb,sg,p,wu[maxm],n; bool visit[maxm]; int mi(int a,int b){ if (b==0) return 1; if (b==1) return a%p; int mm=mi(a,b/2); if (b%2) return mm*mm%p*a%p; else return mm*mm%p; } int calc(){ int i,j,ci=0,a,b,c; memset(fi,0,sizeof(fi)); memset(visit,false,sizeof(visit)); fi[0][0][0]=1; for (i=1;i<=n;++i){ if (visit[i]) continue; wu[++ci]=1;visit[i]=true; for (j=po[i];j!=i;j=po[j]){ visit[j]=true;++wu[ci]; } }for (i=1;i<=ci;++i) for (a=sr;a>=0;--a) for (b=sb;b>=0;--b) for (c=sg;c>=0;--c){ if (a>=wu[i]) fi[a][b][c]=(fi[a][b][c]+fi[a-wu[i]][b][c])%p; if (b>=wu[i]) fi[a][b][c]=(fi[a][b][c]+fi[a][b-wu[i]][c])%p; if (c>=wu[i]) fi[a][b][c]=(fi[a][b][c]+fi[a][b][c-wu[i]])%p; }return fi[sr][sb][sg]; } int main(){ int i,j,m,ans=0; scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&p); n=sr+sb+sg; for (i=1;i<=m;++i){ for (j=1;j<=n;++j) scanf("%d",&po[j]); ans=(ans+calc())%p; }for (j=1;j<=n;++j) po[j]=j; ans=(ans+calc())%p*mi(m+1,p-2)%p; printf("%d\n",ans); }