[博弈搜索][概率][构造状态矩阵]

[COGS 2290 香蕉]

注意到6和15的转移情况以及值都是相同的,推论所有p1 ^ k1 * p2 ^ k2 ...

将指数sort一遍这些数字的转移情况都是相同的,10W以内的数字只有160种状态

构造一个转移矩阵然后做乘法即可。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define maxn 161
using namespace std;
const int md = 998244353;
typedef long long ll;

int Tot;

struct Matrix{
	ll a[maxn][maxn];
	void clear(){memset(a, 0, sizeof a);}
	void set(){clear();for(int i = 1; i <= Tot; i ++)a[i][i] = 1;}
}mat, ans;

Matrix operator*(const Matrix& a, const Matrix &b){
	Matrix c; c.clear();
	for(int i = 1; i <= Tot; i ++)
	    for(int j = 1; j <= Tot; j ++){
			int cnt = 0;
            for(int k = 1; k <= Tot; k ++){
				c.a[i][j] += a.a[i][k] * b.a[k][j];
				cnt ++; if(cnt == 10)c.a[i][j] %= md, cnt = 0;
            }
            c.a[i][j] %= md;
	    }
	return c;
}

int n, m;
#define M 100010
int p[M], Min[M], primes;
bool vis[M];
void preprime(){
	Min[1] = 1;
    for(int i = 2; i <= m; i ++){
		if(!vis[i])p[primes ++] = i, Min[i] = i;
		for(int j = 0; j < primes; j ++){
			if(i * p[j] > m)break;
			vis[i * p[j]] = true;
			Min[i * p[j]] = p[j];
			if(i % p[j] == 0)break;
		}
	}
}

ll hs[M], h[M];
int Log[10], c[10], cnt;

int Amt[maxn], _min[maxn];

Matrix power(Matrix a, int n){
	Matrix ret; ret.set();
	int cnt0 = 0, cnt1 = 0;
	while(n){
		if(n & 1)ret = ret * a;
		n >>= 1;
		a = a * a;
	}
	return ret;
}

int main(){
	freopen("Banana.in", "r", stdin);
	freopen("Banana.out", "w", stdout);
	scanf("%d%d", &n, &m);
	preprime();

	for(int i = 1; i <= m; i ++){
		int nw = i;
		cnt = 0;
		while(nw != 1){
			int to = Min[nw], flag = false;
			for(int j = 1; j <= cnt; j ++)
			    if(Log[j] == to){
					c[j] ++;
					flag = true;
					break;
			    }
			if(!flag)++ cnt, c[cnt] = 1, Log[cnt] = to;
			nw /= to;
		}
		sort(c + 1, c + 1 + cnt);
		ll ret = 0;
		for(int j = 1; j <= cnt; j ++)
			ret = ret * 100 + c[j];
		h[i] = hs[i] = ret;
	}
	
	sort(h + 1, h + 1 + m);
	Tot = unique(h + 1, h + 1 + m) - h - 1;
	
	for(int i = 1; i <= m; i ++){
		int pos = lower_bound(h + 1, h + 1 + Tot, hs[i]) - h;
		Amt[pos] ++;
		if(_min[pos] == 0)
		    _min[pos] = i;
	}
	
	int pos;

	for(int i = 1; i <= Tot; i ++){
		int nw = _min[i], q = sqrt(nw);
		for(int j = 1; j <= Tot; j ++)
			mat.a[j][i] += Amt[j];
		for(int j = 1; j <= q; j ++){
			if(nw == j || nw % j)continue;
			pos = lower_bound(h + 1, h + 1 + Tot, hs[j]) - h;
			mat.a[pos][i] --;
			if(j * j == nw || j == 1)continue;
			pos = lower_bound(h + 1, h + 1 + Tot, hs[nw / j]) - h;
			mat.a[pos][i] --;
		}
	}

	
	ans.clear();
	for(int i = 1; i <= Tot; i ++)
	    ans.a[1][i] = 1;
	ans = ans * power(mat, n);
	cout << (ans.a[1][1] + md) % md<< endl;
    return 0;
}

卡片(card)

【题目描述】鲲鹏国地震后,蛤布斯十分愤怒。他决定前往利沃夫动物园寻求帮助。利沃夫动物园的小象有n张卡片,第i张卡片上有一个数字ai。蛤布斯和小象轮流选择卡片 (不能重复),如果某次选择后已选择的卡片上的数字的gcd为1或者没有卡片可以选择,那么当前角色失败。蛤布斯想知道:1、如果双方都选择最优策略,谁会获胜;2、如果双方都随机选取,蛤布斯获胜的概率。有多组数据。

【输入数据】第一行一个整数t表示数据组数。 每组数据第一行一个整数n,第二行n个整数a1…an。

【输出数据】每组数据输出一行,包含一个整数和一个实数,用一个空格隔开。整数为1表示蛤布斯获胜,0表示小象获胜。实数表示获胜概率,保留4位小数。

【样例输入】

4

5

6 10 15 22 28

5

2 4 8 16 32

4

2 4 8 16

4

1 2 3 4

【样例输出】

0 0.4000

1 1.0000

0 0.0000

1 0.5833

【数据范围】

对于20%的数据,n<=20。

对于60%的数据,ai<=100。

对于100%的数据,t<=10,n<=100,ai<=10^5。

 

30分暴力。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

#define maxn 100010
using namespace std;

#define M 2000000
int n, a[maxn], bo[M];
double f[M];

int gcd(int a, int b){
	if(a == 0)return 0;
	return !b ? a : gcd(b, a % b);
}

bool dfs(int S, int g){
	if(S == 0)return false;
	if(bo[S] != -1)return bo[S];
	int cnt = 0, v = true;
	for(int i = 0; i < n; i ++){
		if(S >> i & 1){
			cnt ++;
			if(g && gcd(g, a[i]) == 1)continue;
			if(a[i] == 1)continue;
			if(g == 0) v &= dfs(S ^ (1 << i), a[i]);
			else v &= dfs(S ^ (1 << i), gcd(g, a[i]));
			f[S] += 1 - f[S ^ (1 << i)];
		}
	}
	bo[S] = !v;
	f[S] /= cnt;
	return !v;
}

int main(){
	freopen("card.in", "r", stdin);
	freopen("card.out", "w", stdout);
	int test;
	scanf("%d", &test);
	while(test --){
		memset(bo, -1, sizeof bo);
		memset(f, 0, sizeof f);
		scanf("%d", &n);
		for(int i = 0; i < n; i ++)
		    scanf("%d", &a[i]);
		int S = 1 << n;
		int flag = dfs(S - 1, 0);
		printf("%d %.4lf\n", flag, (double)f[S - 1]);
	}

    return 0;
}

满分

card:

  20%:状压dp。时间复杂度 O(tn2^n)。

  60%:我们发现一个状态只和当前gcd和已选卡片数量有关,记录这两个信息即可。时间复杂度O(tmn^2),其中m为max(ai)。

  100%:实际上状态数不到mn,而是sigma(ai的约数个数),这个值并不大。如果将ai中相同质因数去掉,状态数最多为n*2^6。时间复杂度O(tn^2*2^6)。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define maxn 100010
using namespace std;
typedef pair<double, int> type;
type f[maxn][110];

int vis[maxn][110], tim, a[maxn], n;
int gcd(int a, int b){return !b ? a : gcd(b, a % b);}

type dfs(int Gcd, int Num){
	if(vis[Gcd][Num] == tim)return f[Gcd][Num];
	type &Nw = f[Gcd][Num], ret; Nw.first = 0, Nw.second = 0;
	vis[Gcd][Num] = tim;
	if(Gcd == 1)return Nw = make_pair(1, 1);
	if(Num == n)return Nw;
	//-----------------------------------------------//
	if(Gcd){
        int cnt = 0; cnt -= Num;
		for(int i = 1; i <= n; i ++)
			cnt += (a[i] % Gcd == 0);
			
		if(cnt > 0){
			Nw = dfs(Gcd, Num + 1);
			Nw.first = cnt * (1 - Nw.first), Nw.second ^= 1;
		}
		
		for(int i = 1; i <= n; i ++){
			int to = gcd(a[i], Gcd);
			if(to == 1 || to == Gcd)continue;
			ret = dfs(to, Num + 1);
			Nw.first += 1 - ret.first, Nw.second |= (ret.second ^ 1);
		}Nw.first /= n - Num;
	}
	else{
		for(int i = 1; i <= n; i ++){
			ret = dfs(a[i], 1);
			Nw.first += 1 - ret.first, Nw.second |= (ret.second ^ 1);
		}Nw.first /= n;
	}
	return Nw;
}

int main(){
	freopen("card.in", "r", stdin);
	freopen("card.out", "w", stdout);
	int test;
	scanf("%d", &test);
	while(test --){
		tim ++;
		scanf("%d", &n); 
		for(int i = 1; i <= n; i ++)
		    scanf("%d", &a[i]);
		type ans = dfs(0, 0);
		printf("%d %.4lf\n", ans.second, ans.first);
	}

    return 0;
}

  

posted @ 2016-04-28 21:44  _Horizon  阅读(311)  评论(0)    收藏  举报