您的位置:首页 > 其它

POJ训练记录1:置换群

2016-05-21 14:00 344 查看

1、POJ 3270 Cow Sorting

传送门

题意

给出一个序列,交换两个数的代价是这两个数的和,问将这个序列排成升序序列的代价。

题解

这是一个入门的置换问题。

首先知道第i大的数最终应该换到第i个位置。假设有一些数,这些数通过一次移位可以使所有的数都换到它应该在的位置,那么将这些数的集合称之为一个整数群,群的大小定义为集合内数的个数。整个序列由大小不同的若干群组成。

考虑将一个群排成升序的代价,假设群的大小为k,有两种交换的方法:

1、群里换,拿群里最小的数t与其他每个数交换,共k-1次,花费为:tmp1=sum+(k-2)*t。

2、将这个数列最小的数m,拉入这个群,与该群最小的数t交换,然后用这个最小的数与其他数交换k-1次,然后再将m与t换回来,这样花费为:tmp2=sum+t+(k+1)m。

显然这个群交换的代价为min(tmp1,tmp2)。

我们利用计数排序,得到每一个数应该在的位置,然后找到每一个群,利用上面的方法计算即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1e5+5;
const int INF=2e9;

int n,Max,Min,j,k,t,sum,ans;
int a[max_n],cnt[max_n];
bool used[max_n];

int main()
{
scanf("%d",&n);
Min=INF;
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
cnt[a[i]]++;
if (a[i]>Max) Max=a[i];
if (a[i]<Min) Min=a[i];
}
for (int i=1;i<=Max;++i)
cnt[i]+=cnt[i-1];
for (int i=1;i<=n;++i)
if (!used[i])
{
j=i;
t=a[i];
k=sum=0;
while (!used[j])
{
k++;
if (t>a[j]) t=a[j];
sum+=a[j];
used[j]=true;
j=cnt[a[j]];
}
if (1<k) ans+=sum;
if (2<k)
{
int tmp1=(k-2)*t;
int tmp2=(k+1)*Min+t;
ans+=min(tmp1,tmp2);
}
}
printf("%d\n",ans);
}


2、POJ 2369 Permutations

传送门

题意

给出一个序列,问最少经过几次置换得到升序。

题解

计算出各个群的大小然后取最小公倍数即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1005;

int n,j,k,tot;
int a[max_n],cnt[max_n],final[max_n];
bool used[max_n];

inline int gcd(int a,int b)
{
if (!b) return a;
else return gcd(b,a%b);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
cnt[a[i]]++;
}
for (int i=1;i<=n;++i)
cnt[i]+=cnt[i-1];
for (int i=1;i<=n;++i)
if (!used[i])
{
j=i;
k=0;
while (!used[j])
{
++k;
used[j]=true;
j=cnt[a[j]];
}
final[++tot]=k;
}
for (int i=2;i<=tot;++i)
{
int t=gcd(final[i-1],final[i]);
final[i]=final[i-1]*final[i]/t;
}
printf("%d\n",final[tot]);
}


3、POJ 1026 Cipher

传送门

题意

给出置换的规则,ai表示i位置经过一次置换变成ai位置上的字符。给出初始的字符串,问经过s次置换后的字符串是什么。

题解

计算出群的大小,知道置换次数之后判断最终置换到哪个位置即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1005;

int n,j,k,tot;
int a[max_n],cnt[max_n],final[max_n];
bool used[max_n];

inline int gcd(int a,int b)
{
if (!b) return a;
else return gcd(b,a%b);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
cnt[a[i]]++;
}
for (int i=1;i<=n;++i)
cnt[i]+=cnt[i-1];
for (int i=1;i<=n;++i)
if (!used[i])
{
j=i;
k=0;
while (!used[j])
{
++k;
used[j]=true;
j=cnt[a[j]];
}
final[++tot]=k;
}
for (int i=2;i<=tot;++i)
{
int t=gcd(final[i-1],final[i]);
final[i]=final[i-1]*final[i]/t;
}
printf("%d\n",final[tot]);
}


4、POJ 1721 CARDS

传送门

题意

每次置换的规则是,a[i]变为a[a[i]],给出末状态以及置换的次数,求初状态。

题解

我的做法比较奇怪,是推出了一个由末状态到初状态的每一步的规律,然后做S次就好了。

网上的标解是说经过一定的次数会出现循环的情况,暴力找出循环节然后判断是那种状态就行了。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1005;

int n,j,k,t,s;
int cnt[max_n],nxt[max_n],ans[max_n];
bool used[max_n];

int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;++i) scanf("%d",&cnt[i]);
t=(n+1)/2;
for (int i=1;i<=s;++i)
{
memset(used,0,sizeof(used));
j=1; k=0;
while (!used[j])
{
++k;
nxt[k]=j;
used[j]=true;
j=cnt[j];
}
for (int i=1;i<=k;++i)
{
int pos=nxt[(i+t-1)%k+1];
ans[pos]=cnt[nxt[i]];
}
for (int i=1;i<=k;++i) cnt[i]=ans[i];
}
for (int i=1;i<=n;++i) printf("%d\n",ans[i]);
}


5、POJ 1286 Necklace of Beads

传送门

题意

有n个珠子的一个项链,求将珠子染成红色蓝色或绿色的不同的方案数(考虑旋转和翻转)

题解

暴力艹标算。

网上有一种很神的结论,但是刚开始不会,就暴力敲了所有的置换,然后利用Burnside和Polya直接算,时间(2n^2)

答案为1m∑i=1mkc1(ai)(m为置换数,k为颜色数)

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long
const int max_n=30;
const int max_m=max_n*2;

int n,m,t,k,L,R;
int a[max_m][max_n],pre[max_n],nxt[max_n],st[max_n];
bool used[max_n];
LL ans;

inline void clear()
{
memset(a,0,sizeof(a));
m=ans=0;
}
inline LL fast_pow(LL a,int p)
{
LL ans=1;
for (;p;p>>=1,a*=a)
if (p&1)
ans*=a;
return ans;
}
inline LL calc(int x)
{
memset(used,0,sizeof(used));
int cnt=0,p;
for (int i=1;i<=n;++i)
if (!used[i])
{
p=i;
cnt++;
while (!used[p])
{
used[p]=true;
p=a[x][p];
}
}
LL ans=fast_pow(3,cnt);
return ans;
}

int main()
{
while (~scanf("%d",&n))
{
if (n==-1) return 0;
if (!n)
{
printf("0\n");
continue;
}
clear();

for (int i=2;i<=n;++i) pre[i]=i-1; pre[1]=n;
for (int i=1;i<n;++i) nxt[i]=i+1; nxt
=1;
for (int i=1;i<=n;++i) st[i]=i;
for (int i=0;i<n;++i)
{
++m;
for (int j=1;j<=n;++j)
a[m][j]=(j+i-1)%n+1;
}

if (n%2==0)
{
for (int i=1;i<=n/2;++i)
{
++m; t=n/2-1;
for (int j=1;j<=n;++j) a[m][j]=st[j];
L=R=i;
for (int j=1;j<=t;++j)
{
L=pre[L]; R=nxt[R];
swap(a[m][L],a[m][R]);
}

++m; t=n/2;
for (int j=1;j<=n;++j) a[m][j]=st[j];
L=i+1; R=i;
for (int j=1;j<=t;++j)
{
L=pre[L]; R=nxt[R];
swap(a[m][L],a[m][R]);
}
}
}
else
{
t=n/2;
for (int i=1;i<=n;++i)
{
++m;
for (int j=1;j<=n;++j) a[m][j]=st[j];
L=R=i;
for (int j=1;j<=t;++j)
{
L=pre[L]; R=nxt[R];
swap(a[m][L],a[m][R]);
}
}
}
for (int i=1;i<=m;++i)
ans+=calc(i);
ans/=m;
printf("%lld\n",ans);
}
}


6、POJ 2409 Let it Bead

传送门

题意

有n个珠子的一个项链,求将珠子染成至多k种颜色的不同的方案数(考虑旋转和翻转)

题解

题面基本上和上道题一样,学习了新的姿势,也是比较厉害的一个结论,具体见:http://www.cnblogs.com/DrunBee/archive/2012/09/10/2678378.html

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long
LL n,k,ans;

inline LL gcd(LL a,LL b)
{
if (!b) return a;
else return gcd(b,a%b);
}
inline LL fast_pow(LL a,LL p)
{
LL ans=1;
for (;p;p>>=1,a*=a)
if (p&1)
ans*=a;
return ans;
}
int main()
{
while (~scanf("%I64d%I64d",&k,&n))
{
if (!n&&!k) return 0;
ans=0;
for (int i=1;i<=n;++i) ans+=fast_pow(k,gcd(i,n));
if (n%2)
{
ans+=fast_pow(k,n/2+1)*n;
}
else
{
ans+=fast_pow(k,n/2)*n/2+fast_pow(k,n/2+1)*n/2;
}
printf("%I64d\n",ans/(2*n));
}
}


7、POJ 2154 Color

传送门

题意

有n个珠子的一个项链,求将珠子染成至多n种颜色的不同的方案数(考虑旋转和翻转)

这道题和上道题的不同就在于n的范围为1e9

题解

用到上一题的结论,本题的答案为

L=1n∑0<=k<nngcd(k,n)

=1n∑d|nnd∑0<=k<n[gcd(k,n)=d]

=∑d|nnd−1∑0<=k<n[gcd(kd,nd)=1]

=∑d|nnd−1∑0<=k<nd[gcd(k,nd)=1]

=∑d|nnd−1ϕ(nd)

刚开始狂T不止,大概是LL的原因吧。

学习了黄学长先筛质数然后根n求phi的姿势,挺不错的。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<ctime>
#include<cmath>
using namespace std;

int T,n,Mod,ans;
int prime[1000005],p[1000005];

inline void _prime()
{
for (int i=2;i<=1000000;++i)
{
if (!p[i]) prime[++prime[0]]=i;
for (int j=1;j<=prime[0]&&i*prime[j]<=1000000;++j)
{
p[i*prime[j]]=1;
if (i%prime[j]==0) break;
}
}
}
inline int _phi(int x)
{
int ans=x;
for (int i=1;prime[i]<=sqrt(x);++i)
if (x%prime[i]==0)
{
ans=(ans-ans/prime[i]);
while (x%prime[i]==0) x/=prime[i];
}
if (x!=1) ans=(ans-ans/x);
return ans%Mod;
}
inline int fast_pow(int a,int p)
{
int ans=1; a%=Mod;
for (;p;p>>=1,a=a*a%Mod)
if (p&1)
ans=ans*a%Mod;
return ans;
}

int main()
{
_prime();
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&Mod);
ans=0;
for (int i=1;i*i<=n;++i)
if (n%i==0)
{
ans=(ans+_phi(i)*fast_pow(n,n/i-1)%Mod)%Mod;
if (i*i!=n) ans=(ans+_phi(n/i)*fast_pow(n,i-1)%Mod)%Mod;
}
printf("%d\n",ans);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: