POJ训练记录1:置换群

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>
#include<ctime>
using namespace std;

const int max_n=205;

int n,t,len,j,k;
int cnt[max_n],seq[max_n];
bool used[max_n];
char s[max_n],ans[max_n];

int main()
{
	while (~scanf("%d",&n))
	{
		if (!n) return 0;
		for (int i=1;i<=n;++i) scanf("%d",&cnt[i]);
		while (~scanf("%d",&t))
		{
			if (!t) break;
			gets(s);
			len=strlen(s);
			for (int i=len;i<=n;++i) s[i]=' ';
			
			memset(used,0,sizeof(used));
			for (int i=1;i<=n;++i)
				if (!used[i])
				{
					j=i;
					k=0;
					while (!used[j])
					{
						++k;
						seq[k]=j;
						used[j]=true;
						j=cnt[j];
					}
					int nxt=t%k;
					for (int l=1;l<=k;++l)
					{
						int pos=(l+nxt)%k;
						if (!pos) pos=k;
						ans[seq[pos]]=s[seq[l]];
					}
				}
			for (int i=1;i<=n;++i)
				printf("%c",ans[i]);
			putchar('\n');
		}
		putchar('\n');
	}
}

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)
答案为 1 m ∑ i = 1 m k c 1 ( a i ) {1\over m} \sum\limits_{i=1}^m k^{c_1(a_i)} m1i=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[n]=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 = 1 n ∑ 0 &lt; = k &lt; n n g c d ( k , n ) L={1\over n}\sum\limits_{0&lt;=k&lt;n}n^{gcd(k,n)} L=n10<=k<nngcd(k,n)
= 1 n ∑ d ∣ n n d ∑ 0 &lt; = k &lt; n [ g c d ( k , n ) = d ] ={1\over n}\sum\limits_{d|n}n^d\sum\limits_{0&lt;=k&lt;n}[gcd(k,n)=d] =n1dnnd0<=k<n[gcd(k,n)=d]
= ∑ d ∣ n n d − 1 ∑ 0 &lt; = k &lt; n [ g c d ( k d , n d ) = 1 ] =\sum\limits_{d|n}n^{d-1}\sum\limits_{0&lt;=k&lt;n}[gcd({k\over d},{n\over d})=1] =dnnd10<=k<n[gcd(dk,dn)=1]
= ∑ d ∣ n n d − 1 ∑ 0 &lt; = k &lt; n d [ g c d ( k , n d ) = 1 ] =\sum\limits_{d|n}n^{d-1}\sum\limits_{0&lt;=k&lt;{n\over d}}[gcd(k,{n\over d})=1] =dnnd10<=k<dn[gcd(k,dn)=1]
= ∑ d ∣ n n d − 1 ϕ ( n d ) =\sum\limits_{d|n}n^{d-1}\phi({n\over d}) =dnnd1ϕ(dn)
刚开始狂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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值