Necklace of Beads(poj 1286)
原题链接
题目类型:置换群和polya定理
思考过程:求解n的时候,可否利用n-1来进行求解?->由于题目中有关于对称性的限制,因此可能会存在一定的问题。
使用floya定理,那么如何去求解关于置换的个数的问题呢?
思路一:(自己的推导)
对于沿着对称轴形成的置换,n个点共有n条对称轴,若n为奇数,则n个对称轴所形成的置换都是分解为(n+1)/2。若n为偶数,则n/2个对称轴(对称轴不经过点)形成的置换分解为n/2,n/2个对称轴(经过点)形成的置换分解为n/2+1个。
对于旋转形成的置换,若n为偶数,则1个置换被分解为n个,n/2-1个置换(旋转的次数为偶数)被分解为2个,n/2个置换(旋转的次数为奇数)被分解为1个。
//WA
#include<iostream>
using namespace std;
#define NMAX 30
long long e3[NMAX];
long long n;
int main() {
e3[0] = 1;
for (int i = 1; i < NMAX; i++) e3[i] = e3[i - 1] * 3;
while (1) {
cin >> n;
if (n == -1) break;
if (n == 0) cout << 1 << endl;
else if (n % 2) {
cout << (n * e3[(n + 1) / 2] + e3[n] + (n - 1) * e3[1]) / (2 * n) << endl;
}
else cout << (n / 2 * e3[n / 2] + n / 2 * e3[n / 2 + 1] + e3[n] + (n / 2 - 1) * e3[2] + n / 2 * e3[1]) / (2 * n) << endl;
}
}
在提交过程中,发现是错误的。
思路二:
对于沿着对称轴所形成的置换,得到的结论是正确的。
但是对于旋转形成的置换,则是错误的。
对于编号为x的点,若经过旋转i次的置换,会变为x+i。则以1为起点的循环节可以表示为1->1+i->1+2i->1+3i…->1。假设循环节的长度为k,则有ki+1同余1模上n。因此有n|ki,k最小值为n/gcd(n,i),即每一个循环节的长度都为n/gcd(n,i),因此共有gcd(n,i)个循环节。
思路参考链接
#include<iostream>
using namespace std;
#define NMAX 30
long long e3[NMAX];
long long n;
long long gcd(int y, int x) {
return y % x == 0 ? x : gcd( x , y % x);
}
int main() {
e3[0] = 1;
for (int i = 1; i < NMAX; i++) e3[i] = e3[i - 1] * 3;
while (1) {
cin >> n;
if (n == -1) break;
if (n == 0) cout << 0 << endl;
else if (n % 2) {
long long res = n * e3[(n + 1) / 2];
for (long long i = 1; i <= n; i++) {
res += e3[gcd(i, n)];
}
cout << res / (2*n) << endl;
}
else {
long long res = n / 2 * e3[n / 2] + n / 2 * e3[n / 2 + 1];
for (long long i = 1; i <= n; i++) {
res += e3[gcd(i, n)];
}
cout << res / (2 * n) << endl;
}
}
}
参考链接:
旋转i次,对于循环节数为gcd(i,n)的证明
polya定理
Let it Bead(poj 2409)
原题链接
不同的手链,一定是经过旋转,翻转之后仍然不相同的个数,因此,本题在本质上是和上一题一样的,但是颜色色个数不再只是一个常量。
需要注意的是,pow没有参数类型都为int的函数原型,因此需要先将传入的参数首先转化为float.
#include<iostream>
#include<cmath>
using namespace std;
long long s;
long long c;
long long gcd(long long y, long long x) {
return y % x == 0 ? x : gcd( x , y % x);
}
int main() {
while (1) {
cin >> c >> s;
if (c == 0 && s == 0) break;
if (s == 0) cout << 0 << endl;
else if (s % 2) {
long long res = s * (long long)pow((float)c, (float)((s + 1) / 2));
for (long long i = 1; i <= s; i++) {
res += (long long)pow((float)c, (float)gcd(i, s));
}
cout << res / (2*s) << endl;
}
else {
long long res = s / 2 * (long long)pow((float)c, (float)(s / 2)) + s / 2 * (long long)pow((float)c, (float)(s / 2 + 1));
for (long long i = 1; i <= s; i++) {
res += (long long)pow((float)c, (float)gcd(i, s));
}
cout << res / (2 * s) << endl;
}
}
}
Cow Sorting(poj 3270)
原题链接
思路一:
思考过程:本质上是一个置换,且最终的排列顺序是已知的,即是递增的顺序,可否使用贪心的策略,使得当前脾气最大的牛只换一次。
//WA代码
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
#define NMAX 10004
#define TMAX 100005
int N;
int temper[NMAX];
int pro[NMAX];
int lot[TMAX];
long long res = 0;
int main() {
cin >> N;
for (int i = 0; i < N; i++) {
cin >> temper[i];
pro[i] = temper[i];
lot[temper[i]] = i;
}
sort(pro, pro + N);
for (int i = N - 1; i > 0 ; i--) {
res += (pro[i] + temper[i]);
lot[temper[i]] = lot[pro[i]];
swap(temper[i], temper[lot[pro[i]]]);
}
cout << res;
}
思路二:
参考思路
首先,这一定是一个置换,但是该置换只能通过两两相互交换得到,问题便在于如何交换使得交换的成本是最小的。将视线集中在置换环上,交换的双方都是位于同一置换环,是可以达到最终的置换效果的,即置换环内进行交换。
在一个置换环中,每一个元素都至少会被交换一次,因此为使得值最小,可以每次都使用置换环中最小的那个来进行交换,即值为X1=sum+(len-2)*min。
假如置换环中的最小元素还是很大,考虑将整个区间内最小的值Amin首先交换进入置换环中,利用Amin来进行置换环内的交换,其值有X2=sum-min+Amin+(len-2)*Amin+2(Amin+min)。
因此对于一个置换环,其值X为min(X1,X2).
那么问题便转化为,如何去求解置换环,以及置换环上的极值。
首先我们需要求解得出置换sub,sub[i]代表的是第i位需要由第sub[i]位来进行替换。逐个扫描sub数组,使用vis代表是否访问过,对于没有访问过的,使用while(j!=i)的形式来求其置换环,在遍历置换环的过程中,维护最值、和,最后输出即可。
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
#define NMAX 10004
#define TMAX 100005
int N;
int temper[NMAX];
int pro[NMAX];
int lot[TMAX];
int sub[NMAX];
int vis[NMAX];
long long res = 0;
int main() {
cin >> N;
for (int i = 0; i < N; i++) {
cin >> temper[i];
pro[i] = temper[i];
lot[temper[i]] = i;
}
sort(pro, pro + N);
int Amin = pro[0];
memset(vis, 0, sizeof(vis));
for (int i = 0; i < N; i++) sub[i] = lot[pro[i]];
for (int i = 0; i < N; i++) {
if (!vis[i]) {
vis[i] = 1;
int Min = 1000000, sum = temper[i], len = 1;
int j = sub[i];
while (j != i) {
vis[j] = 1;
len++;
if (temper[j] < Min) Min = temper[j];
sum += temper[j];
j = sub[j];
}
if (len == 1) continue;//长度为1的时候不需要操作
int X1 = sum + (len - 2) * Min;
int X2 = sum - Min + Amin + (len - 2) * Amin + 2 * (Amin + Min);
res += min(X1, X2);
}
}
cout << res << endl;
}