博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

ZROI week3

作业

poj 1091 跳蚤

容斥原理。

考虑能否跳到旁边就是卡牌的\(gcd\)是否是1,可以根据裴蜀定理证明。

考虑正着做十分的麻烦,所以倒着做,也就是用\(M^N - (不合法)\)即可。

不合法显然就是\(gcd\)不为1的情况,那么我们考虑枚举\(gcd\)\((1 \leq gcd \leq 15)\),第\(n + 1\)个数一直是\(m\)所以不用理它。

考虑每个\(gcd\)的贡献,如果所有的都能被整除,那么产生的方案数就是:

\(({m \over gcd})^n\)

表示在小于等于\(m\)的数中有多少能够被当前的\(gcd\)整除,那么每个位置就有这么多数字可以选择,就转化成了容斥原理的问题了,\(dfs\)枚举\(m\)的质因数统计即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
#define int long long
int n,m;
const int MAXN = 1e5 + 10;
int Ans;
int p[MAXN];
int ans;
int div (int now) {
	int res = 0;
	for(int i = 2;i * i <= now; ++i) {
		if(now % i == 0) {
			p[++res] = i;
			now /= i;
			while(now % i == 0) now /= i;
		}
	}
	if(now > 1) p[++res] = now;
	return res;
}


int pow_mod(int a,int b) {
	int res = 1;
	while(b) {
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}

void dfs(int num,int y,int now) {
	if(now > m) return;
	if(num == ans + 1) {
		if(y) {
			if(y & 1) {
				Ans -= pow_mod(m / now,n);
			}
			else {
				Ans += pow_mod(m / now,n);
			}
		}
		return;
	}
	dfs(num + 1,y,now);
	dfs(num + 1,y + 1,now * p[num]);
}

signed main () {
	cin >> n >> m;
	Ans = pow_mod(m,n);
	//cout<<Ans<<endl;
	ans = div(m);
	//cout<<ans<<endl;
	dfs(1,0,1);
	cout<<Ans<<endl;
	return 0;
}

ps:这题为啥不用取模?真是让人难以琢磨。

hdu 5121 just a mistake

期望\(dp\)+容斥原理

题目大意就是让你随机构造一个排列,然后判断是否与集合中的点有连边然后一个一个的连接。

乍一看十分难做,第二眼还是十分难做,做了\(1h23min\)。。。

首先考虑如何统计贡献,一般的方程就是如果第\(i\)\(p_i\)的时候,某些贡献是多少,由于这个问题拓展到了树上,那么就是设\(f_{i,j}\)表示以\(i\)为根的子树中,\(i\)排在第\(j\)位且必须选的方案数。

那么子树中的点如果排在\(i\) 的前面就不能选,因为\(i\)必选,贡献就得减去它。

如果在后面,选了\(i\)之后也不能选它,然后组合数统计答案即可。

具体就是以每个点为根去做搜索,统计出答案之后相加即可,由于期望的线性性。

再来说说具体怎么计算答案,枚举\(y\)这棵子树的1到siz[y]的集合中前\(k\)个插入到\(i\)前面,后面的插在后面,乘上\(f_{i,j}\),在乘上选位置的方案数。

选位置的方案数就是\(C_{n + m}^{n}\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
const int MAXN = 210;
int C[MAXN][MAXN];
int siz[MAXN];
int f[MAXN][MAXN];
int lnk[MAXN];
int fac[MAXN];
const int mod = 1e9 + 7;
int cnt;
int head[MAXN << 1];
void pre() {
	memset(C,0,sizeof C);
	memset(fac,0,sizeof fac);
	C[0][0] = 1;
	fac[0] = 1;
	for(int i = 1;i <= 200; ++i) {
		C[i][0] = C[i][i] = 1;
		fac[i] = (fac[i - 1] * i) % mod;
		for(int j = 1;j <= i; ++j) {
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
		}
	}
	return;
}
int T,n,x,y;
int ans;
struct edge {
	int to;
	int nxt;
}e[MAXN << 1];
void add(int u,int v) {
	e[++cnt].to = v;
	e[cnt].nxt = head[u];
	head[u] = cnt;
	return;
}

void Add(int u,int v) {
	add(u,v);
	add(v,u);
}

void dfs(int x,int fa) {
	f[x][0] = 0;f[x][1] = 1;
	siz[x] = 1;
	for(int i = head[x];i;i=e[i].nxt) {
		int y = e[i].to;
		if(y == fa) continue;
		dfs(y,x);
		int all_state = fac[siz[y]];
		int tmp = siz[y];
		int sum_state = siz[x] + siz[y];
		siz[x] += siz[y];
		memset(lnk,0,sizeof lnk);
		for(int j = 0;j <= siz[y]; ++j,--tmp) {
			all_state = (all_state + mod - f[y][j]) % mod;
			for(int k = 1;k <= sum_state - siz[y]; ++k) {
				lnk[j + k] = ((lnk[j + k] + all_state * f[x][k] % mod * C[j + k - 1][j] % mod * C[sum_state - j - k][tmp] % mod) % mod + mod) % mod;
			}
		}
		for(int j = 0;j <= sum_state; ++j) {
			f[x][j] = lnk[j];
		}
	}
}
int cas;
signed main () {
	scanf("%lld",&T);
	pre();
	while(T--) {
		scanf("%lld",&n);
		memset(head,0,sizeof head);
		cnt = 0;
		memset(f,0,sizeof f);
		memset(lnk,0,sizeof lnk);
		memset(siz,0,sizeof siz);
		for(int i = 1;i < n; ++i) {
			scanf("%lld %lld",&x,&y);
			Add(x,y);
		}
		ans = 0;
		for(int i = 1;i <= n; ++i) {
			memset(f,0,sizeof f);
			dfs(i,0);
			for(int j = 1;j <= n; ++j) {
				ans = (ans + f[i][j]) % mod;
			}
		}
		printf("Case #%lld: %lld\n",++cas,ans);
	}
	return 0;
}

ps:组合数没取模调了半天没看出来。。。

hdu 4336 Card Collector

裸.容斥原理

一坨式子懒得写了.

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define db double
int n,tmp;
db p[21];
db ans;
db sum;
void dfs(int now,db P,int st) {
	if(now == tmp) {
		ans += 1.0 / P;
		return;
	}
	for(int i = st;i <= n; ++i) {
		dfs(now + 1,P + p[i],i + 1);
	}
}
int main () {
	while(~scanf("%d",&n)) {
		sum = 0;
		for(int i = 1;i <= n; ++i) {
			scanf("%lf",&p[i]);
			sum += 1.0 / p[i];
		}
		for(int i = 2;i <= n; ++i) {
			tmp = i;
			ans = 0;
			dfs(0,0,1);
			//cout<<ans<<endl;
			if((i & 1) == 0) {
				ans = -ans;
			}
			sum += ans;
		}
		printf("%.6lf\n",sum);
	}
	return 0;
}

hdu 6314

容斥原理。

挖挫...这题真的是卡常数??

首先至少这种问题在高中就学过用补集去算,设\(f_{n,m}\)表示没有任何一行一列有全是黑色。

简单推一下这个就是:

\(f_{n,m} = \sum_{i = 0}^{n} \sum_{j = 0}^{m} C_{n}^{i} \times C_{m}^{j} \times (-1)^{i + j} \times 2^{(n - i) \times (m - j)}\)

答案首先就是:

\(Ans = \sum_{i = 0}^{n - A} \sum_{j = 0}^{m - B} C_{n}^{i} \times C_{m}^{j} \times f_{i,j}\)

\(\;\;\;\;\;\;\;= \sum_{i = 0}^{n - A} \sum_{j = 0}^{m - B} C_{n}^{i} \times C_{m}^{j} \sum_{u = 0}^{i} \sum_{v = 0}^{j} C_{i}^{u} \times C_{j}^{v} \times (-1)^{u + v} \times 2^{(i - u) \times (j - v)}\)

考虑哪些是可以直接处理出来的,例如这个:

\(\sum \sum C_{n}^{i} \times C_{m}^{j}\)

就可以直接算一手...

考虑后面的怎么算?

组合数展开就行了,预处理出形如\(i + j = k\)并且\(i\)的值大于等于\(l\) 的和,然后处理\(g_{i,j} = {2^{i \times j} \over i! \times j!}\)

枚举\(u,v\)即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
#define int long long
const int mod = 998244353;
const int MAXN = 3010;
int C[MAXN][MAXN];
int g[MAXN][MAXN];
int fac[MAXN];
int inv[MAXN];
int n,m,A,B;int ans;
int two[MAXN * MAXN];
int pow_mod(int a,int b) {
    int res = 1;
    while(b) {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
void pre() {
    fac[0] = 1;
    inv[0] = 1;
    two[0] = 1;
    for(int i = 1;i <= MAXN * MAXN; ++i) {
        two[i] = two[i - 1] * 2 % mod;
    }
    for(int i = 1;i <= MAXN; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = pow_mod(fac[i],mod - 2);
    }
    for(int i = 0;i <= 3000; ++i) {
        for(int j = i;j >= 0; --j) {
            C[i][j] = (((i - j) & 1 ? (mod - (inv[j] * inv[i - j] % mod)) : (inv[j] * inv[i - j] % mod)) + C[i][j + 1]) % mod;
            //cout<<C[i][j]<<endl;
        }
    }
    for(int i = 0;i <= 3000; ++i) {
        for(int j = 0;j <= 3000; ++j) {
            g[i][j] = two[i * j] * inv[i] % mod * inv[j] % mod;
        //if(j <= 30 and i == 0) cout<<g[i][j]<<endl;
        }
    }
}
int cnt = 0;
//i == 1 and j == 1
signed main () {
    pre();
    while(~scanf("%d %d %d %d",&n,&m,&A,&B)) {
        ans = 0;
        for(int i = 0;i <= n - A; ++i) {
            for(int j = 0;j <= m - B; ++j) {
                ans = ((ans + C[n - i][A] * C[m - j][B] % mod * g[i][j] % mod) % mod + mod) % mod;
                //cout<<ans<<endl;
            }
        }
        //n : 3 m : 4 A : 1 B : 2
        //cout<<g[1][1]<<endl;
        //cout<<two[1]<<' '<<inv[1]<<' '<<inv[1]<<endl;
        //cout<<C[2][1]<<' '<<C[3][2]<<endl;
        ans = ans * fac[n] % mod * fac[m] % mod;
        ans = (ans % mod + mod) % mod;
        cout<<ans<<endl;
    }
    return 0;
}
			

ps:处理\(two\)数组的时候我居然乘的是2...

但是...\(AC\)不了...

卡常!

卡了三点常就过了:

  • 去掉#define int long long改为1ll * ...去算
  • int变为register int
  • 函数前面加上inline

然后呢?就过了。。。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
#define rint register int
const int mod = 998244353;
const int MAXN = 3010;
int C[MAXN][MAXN];
int g[MAXN][MAXN];
int fac[MAXN];
int inv[MAXN];
int n,m,A,B;int ans;
int two[MAXN * MAXN];
inline int pow_mod(int a,int b) {
	int res = 1;
	while(b) {
		if(b & 1) res = 1ll * res * a % mod;
		a = 1ll * a * a % mod;
		b >>= 1;
	}
	return res;
}
inline void pre() {
	fac[0] = 1;
	inv[0] = 1;
	two[0] = 1;
	for(rint i = 1;i <= MAXN * MAXN; ++i) {
		two[i] = two[i - 1] * 2 % mod;
	}
	for(rint i = 1;i <= MAXN; ++i) {
		fac[i] = 1ll * fac[i - 1] * i % mod;
		inv[i] = pow_mod(fac[i],mod - 2);
	}
	for(rint i = 0;i <= 3000; ++i) {
		for(rint j = i;j >= 0; --j) {
			C[i][j] = (((i - j) & 1 ? (mod - (1ll * inv[j] * inv[i - j] % mod)) : (1ll * inv[j] * inv[i - j] % mod)) + C[i][j + 1]) % mod;
			//cout<<C[i][j]<<endl;
		}
	}
	for(rint i = 0;i <= 3000; ++i) {
		for(rint j = 0;j <= 3000; ++j) {
			g[i][j] = 1ll * two[i * j] * inv[i] % mod * inv[j] % mod;
		//if(j <= 30 and i == 0) cout<<g[i][j]<<endl;
		}
	}
}
int cnt = 0;
//i == 1 and j == 1
signed main () {
	pre();
	while(~scanf("%d %d %d %d",&n,&m,&A,&B)) {
		ans = 0;
		for(rint i = 0;i <= n - A; ++i) {
			for(rint j = 0;j <= m - B; ++j) {
				ans = ((ans + 1ll * C[n - i][A] * C[m - j][B] % mod * 1ll * g[i][j] % mod) % mod + mod) % mod;
				//cout<<ans<<endl;
			}
		}
		//n : 3 m : 4 A : 1 B : 2
		//cout<<g[1][1]<<endl;
		//cout<<two[1]<<' '<<inv[1]<<' '<<inv[1]<<endl;
		//cout<<C[2][1]<<' '<<C[3][2]<<endl;
		ans = ans * 1ll * fac[n] % mod * fac[m] % mod;
		ans = (ans % mod + mod) % mod;
		printf("%d\n",ans);
	}
	return 0;
}
			

hdu 5838

状压+容斥

#include <bits/stdc++.h>
using namespace std;
#define int long long
//const int mod = 772002;
const int mod = 12345678;
int cnt;
int n,m;
int cas;
int ans;
int dx[8] = {1,-1,0,0,1,-1,1,-1};
int dy[8] = {0,0,1,-1,1,1,-1,-1};
int nx[10];
int ny[10];
int f[30][1 << 10];
int sta[1 << 10];
char s[7][7];
int vis[7][7];

int ok(int x,int y) {
    return x >= 0 && x < n && y >= 0 && y < m;
}

int dp() {
    cnt = 0;
    for(int i = 0;i < n; ++i) {
        for(int j = 0;j < m; ++j) {
            if(s[i][j] == 'X') {
                nx[cnt] = i;
                ny[cnt++] = j;
            }
        }
    }
    memset(sta,0,sizeof sta);
    for(int i = 0;i < (1 << cnt); ++i) {
        memset(vis,0,sizeof vis);
        for(int j = 0;j < cnt; ++j) {
            if(~i & (1 << j)) {
                for(int k = 0;k < 8; ++k) {
                    int Nx = nx[j] + dx[k];
                    int Ny = ny[j] + dy[k];
                    if(!ok(Nx,Ny)) continue;
                    vis[Nx][Ny] = 1;
                }
                vis[nx[j]][ny[j]] = 1;
            }
        }
        
        for(int j = 0;j < n; ++j) {
            for(int k = 0;k < m; ++k) {
                if(!vis[j][k]) {
                    sta[i] ++;
                }
            }
        }
    }
    int all_sta = n * m;
    memset(f,0,sizeof f);
    f[0][0] = 1;
    for(int i = 1; i <= all_sta; ++i) {
        for(int j = 0;j < (1 << cnt); ++j) {
            for(int k = 0;k < cnt; ++k) {
                if(j & (1 << k)) {
                    f[i][j] = (f[i][j] + f[i - 1][j ^ (1 << k)]) % mod;
                }
            }
            f[i][j] = ((f[i][j] + 1ll * f[i - 1][j] * max(0ll,1ll * sta[j] - i + 1) % mod) % mod + mod) % mod;
        }
    }
    return f[all_sta][(1 << cnt) - 1];
}

void dfs(int now,int lim,int f) {
    if(lim == m) {
        ans = ((ans + 1ll * dp() * f % mod) % mod + mod) % mod;
        return;
    }
    else if(now == n) {
        dfs(0,lim + 1,f);
        return;
    }
    else {
        dfs(now + 1,lim,f);
    }
    if(s[now][lim] != 'X') {
        for(int j = 0;j < 8; ++j) {
            int new_x = now + dx[j];
            int new_y = lim + dy[j];
            if(ok(new_x,new_y) && s[new_x][new_y] == 'X') goto state;
        }
        s[now][lim] = 'X';
        dfs(now + 1,lim,-f);
        s[now][lim] = '.';
        state : ;
    }
}
signed main () {
    while(~scanf("%lld %lld",&n,&m)) {
        ans = 0;
        for(int i = 0;i < n; ++i) {
            scanf("%s",s[i]);
        }
        dfs(0,0,1);
        //cout<<"Case #"<<(++cas)<<':'<<' '<<(((ans % mod + mod) % mod) == m ? 0 : ((ans % mod + mod) % mod))<<endl;
        cout<<((ans % mod + mod) % mod)<<endl;
    }
    return 0;
}
/*
2 4
.X..
...X
4 2
X.
..
..
X.
1 2
XX
*/

/*
Case #1: 2100
Case #2: 2520
Case #3: 0
*/
posted @ 2019-01-03 17:29  Allorkiya  阅读(194)  评论(0编辑  收藏  举报