传送门
题意
给出一个序列,交换两个数的代价是这两个数的和,问将这个序列排成升序序列的代价。
题解
这是一个入门的置换问题。
首先知道第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
#include
#include
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]a[j]) t=a[j];
sum+=a[j];
used[j]=true;
j=cnt[a[j]];
}
if (1
传送门
题意
给出一个序列,问最少经过几次置换得到升序。
题解
计算出各个群的大小然后取最小公倍数即可。
代码
#include
#include
#include
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]);
}
传送门
题意
给出置换的规则,ai表示i位置经过一次置换变成ai位置上的字符。给出初始的字符串,问经过s次置换后的字符串是什么。
题解
计算出群的大小,知道置换次数之后判断最终置换到哪个位置即可。
代码
#include
#include
#include
#include
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');
}
}
传送门
题意
每次置换的规则是,a[i]变为a[a[i]],给出末状态以及置换的次数,求初状态。
题解
我的做法比较奇怪,是推出了一个由末状态到初状态的每一步的规律,然后做S次就好了。
网上的标解是说经过一定的次数会出现循环的情况,暴力找出循环节然后判断是那种状态就行了。
代码
#include
#include
#include
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]);
}
传送门
题意
有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=1∑mkc1(ai)(m为置换数,k为颜色数)
代码
#include
#include
#include
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个珠子的一个项链,求将珠子染成至多k种颜色的不同的方案数(考虑旋转和翻转)
题解
题面基本上和上道题一样,学习了新的姿势,也是比较厉害的一个结论,具体见:http://www.cnblogs.com/DrunBee/archive/2012/09/10/2678378.html
代码
#include
#include
#include
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));
}
}
传送门
题意
有n个珠子的一个项链,求将珠子染成至多n种颜色的不同的方案数(考虑旋转和翻转)
这道题和上道题的不同就在于n的范围为1e9
题解
用到上一题的结论,本题的答案为
L = 1 n ∑ 0 < = k < n n g c d ( k , n ) L={1\over n}\sum\limits_{0<=k<n}n^{gcd(k,n)} L=n10<=k<n∑ngcd(k,n)
= 1 n ∑ d ∣ n n d ∑ 0 < = k < n [ g c d ( k , n ) = d ] ={1\over n}\sum\limits_{d|n}n^d\sum\limits_{0<=k<n}[gcd(k,n)=d] =n1d∣n∑nd0<=k<n∑[gcd(k,n)=d]
= ∑ d ∣ n n d − 1 ∑ 0 < = k < n [ g c d ( k d , n d ) = 1 ] =\sum\limits_{d|n}n^{d-1}\sum\limits_{0<=k<n}[gcd({k\over d},{n\over d})=1] =d∣n∑nd−10<=k<n∑[gcd(dk,dn)=1]
= ∑ d ∣ n n d − 1 ∑ 0 < = k < n d [ g c d ( k , n d ) = 1 ] =\sum\limits_{d|n}n^{d-1}\sum\limits_{0<=k<{n\over d}}[gcd(k,{n\over d})=1] =d∣n∑nd−10<=k<dn∑[gcd(k,dn)=1]
= ∑ d ∣ n n d − 1 ϕ ( n d ) =\sum\limits_{d|n}n^{d-1}\phi({n\over d}) =d∣n∑nd−1ϕ(dn)
刚开始狂T不止,大概是LL的原因吧。
学习了黄学长先筛质数然后根n求phi的姿势,挺不错的。
代码
#include
#include
#include
#include
#include
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);
}
}