@一句话题解

topcoder - SRM765Round1 D1L3:只有单位代价最大的才可能不全选完,所以按单位代价排序然后从小到大作个背包即可。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
class SteelMill{
	private:
		#define MAXN 100
		#define MAXM 1000000
		#define INF (1LL<<60)
		struct node{
			int siz; ll spc, kgc;
			node(int _s=0, int _sc=0, int _kc=0) : siz(_s), spc(_sc), kgc(_kc) {}
			friend bool operator < (node a, node b) {
				return a.kgc < b.kgc;
			}
		}a[MAXN + 5];
		ll f[MAXM + 5]; int n;
	public:
		ll cheapest(int goal, vector<int>shipmentCost, vector<int>shipmentSize, vector<int>costPerKg) {
			n = shipmentCost.size();
			for(int i=0;i<n;i++)
				a[i] = node(shipmentSize[i], shipmentCost[i], costPerKg[i]);
			ll ans = INF; sort(a, a + n); f[0] = 0;
			for(int i=1;i<goal;i++) f[i] = INF;
			for(int i=0;i<n;i++) {
				ll c = a[i].spc + a[i].kgc * a[i].siz, t = a[i].spc;
				for(int j=goal-1;j>=goal-a[i].siz&&j>=0;j--)
					t += a[i].kgc, ans = min(ans, f[j] + t);
				for(int j=goal-1;j>=a[i].siz;j--)
					f[j] = min(f[j], f[j-a[i].siz] + c);
			}
			return ans;
		}
};

topcoder - TCO19JapanRegionalRound D1L2:类比卡特兰数看成 H*A 的方格恰好 C 次穿过 y = x 这条线,然后直接 dp[H][A][C],转移枚举下一次在哪里穿过然后乘个卡特兰数。|H-A|总为定值所以记忆化搜索。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
class ChangesInTheLead{
	#define MOD 1000000007
	#define MAXN 250
	public :
	int c[2*MAXN + 5][2*MAXN + 5], f[MAXN + 5][MAXN + 5];
	void init() {
		for(int i=0;i<=2*MAXN;i++) {
			c[i][0] = 1;
			for(int j=1;j<=i;j++)
				c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;
		}
		for(int i=0;i<=MAXN;i++) {
			for(int j=0;j<=i;j++)
				f[i][j] = (c[i+j][i] + MOD - c[i+j][i+1]) % MOD;
		}
		f[0][0] = 0;
	}
	int dp[MAXN + 5][MAXN + 5][MAXN + 5];
	int get(int A, int B, int C) {
		if( dp[A][B][C] != -1 ) return dp[A][B][C];
		if( C == 0 ) return dp[A][B][C] = f[A][B];
		dp[A][B][C] = 0;
		for(int i=1;i<=A&&i<=B;i++)
			dp[A][B][C] = (dp[A][B][C] + 1LL*get(B - i, A - i, C - 1)*f[i][i]%MOD) % MOD;
		return dp[A][B][C];
	}
	int count(int H, int A, int C) {
		init(); memset(dp, -1, sizeof dp);
		if( H == 0 && A == 0 && C == 0 ) return 1;
		else return (get(H, A, C) + get(A, H, C)) % MOD;
	}
};

topcoder - 2017TCOAlgorithmFinal D1L2:忽略空位,结论是对于 ABA....BA 这种交替出现(要求极长,即不能再往外延伸)的段,只有最后一个字符对应的玩家才会移动这个段中的棋子。因此直接从后往前 dp,记录两个玩家能够移动的步数之差。

#include <cstdio>
#include <iostream>
using namespace std;
class GameOfTokens{
	private :
	#define MOD 1000000007
	#define MAXN 50
	#define MAXM 2500
	bool flag;
	int f[2][MAXN + 5][2*MAXM + 5];
	int t[2][MAXN + 5][2*MAXM + 5];
	int n, m;
	void get_temp() {
		for(int i=0;i<=n;i++)
			for(int j=0;j<=2*m;j++) {
				t[0][i][j] = f[0][i][j], f[0][i][j] = 0;
				t[1][i][j] = f[1][i][j], f[1][i][j] = 0;
			}
	}
	void trans(int op) {
		if( op == -1 ) {
			for(int i=0;i<=n;i++)
				for(int j=0;j<=2*m;j++) {
					if( j + i <= 2*m )
						f[0][i][j+i] = (f[0][i][j+i] + t[0][i][j]) % MOD;
					if( j - i >= 0 )
						f[1][i][j-i] = (f[1][i][j-i] + t[1][i][j]) % MOD;
				}
		}
		else {
			if( flag ) {
				f[op][1][m] = (f[op][1][m] + 1) % MOD;
			}
			for(int i=0;i<=n;i++)
				for(int j=0;j<=2*m;j++) {
					if( i + 1 <= n )
						f[op][i+1][j] = (f[op][i+1][j] + t[op][i][j]) % MOD;
					if( i )
						f[op][0][j] = (f[op][0][j] + t[!op][i][j]) % MOD;
				}
			for(int j=0;j<=2*m;j++) {
				f[op][1][j] = (f[op][1][j] + t[!op][0][j]) % MOD;
			}
		}
	}
	public :
	int count(string pattern) {
		n = pattern.size(), m = n*n;
		flag = true;
		for(int i=n-1;i>=0;i--) {
			get_temp();
			if( pattern[i] == '?' )
				trans(0), trans(1), trans(-1);
			else if( pattern[i] == '.' )
				trans(-1);
			else trans(pattern[i] - 'A'), flag = false;
		}
		int ans = 0;
		for(int i=m+1;i<=2*m;i++)
			for(int j=0;j<=n;j++)
				ans = (ans + (f[0][j][i] + f[1][j][i]) % MOD) % MOD;
		return ans;
	}
};

codeforces - 1280D:定义 dp[i][j] 表示以 i 为根已经分割出了 j 个连通块,此时能够获胜的连通块个数与 i 所在连通块的票数差。贪心地想,不能够牺牲能够获胜的连通块个数提高 i 所在连通块的票数差(后者最多贡献 1)。所以以获胜连通块个数为第一关键字,票数差为第二关键字树上背包 dp。经典时间复杂度 O(nm)。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;
typedef pair<int, ll> pil;

const int MAXN = 3000;

struct edge{
	int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
void addedge(int u, int v) {
	edge *p = (++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->nxt = adj[v], adj[v] = p;
}
void clear(int n) {
	for(int i=1;i<=n;i++)
		adj[i] = NULL;
	ecnt = edges;
}

int siz[MAXN + 5], n, m;
ll a[MAXN + 5], b[MAXN + 5];
pil dp[MAXN + 5][MAXN + 5], t[MAXN + 5];

void dfs(int x, int f) {
	siz[x] = 1, dp[x][1] = make_pair(0, b[x] - a[x]);
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == f ) continue;
		dfs(p->to, x);
		int t1 = min(siz[x], m), t2 = min(siz[p->to], m), t3 = min(siz[x] + siz[p->to], m);
		for(int i=t1;i>=1;i--) t[i] = dp[x][i];
		for(int i=t3;i>=1;i--) dp[x][i] = make_pair(-1, -1);
		for(int i=t1;i>=1;i--)
			for(int j=t2;j>=1;j--) {
				if( i + j <= t3 ) {
					int d = (dp[p->to][j].second > 0);
					dp[x][i+j] = max(dp[x][i+j], make_pair(t[i].first + dp[p->to][j].first + d, t[i].second));
				}
				if( i + j - 1 <= t3 ) {
					dp[x][i+j-1] = max(dp[x][i+j-1], make_pair(t[i].first + dp[p->to][j].first, t[i].second + dp[p->to][j].second));
				}
			}
		siz[x] += siz[p->to];
	}
}

void solve() {
	scanf("%d%d", &n, &m), clear(n);
	for(int i=1;i<=n;i++) scanf("%lld", &a[i]);
	for(int i=1;i<=n;i++) scanf("%lld", &b[i]);
	for(int i=1;i<n;i++) {
		int u, v; scanf("%d%d", &u, &v);
		addedge(u, v);
	}
	dfs(1, 0);
	printf("%d
", dp[1][m].first + (dp[1][m].second > 0));
}

int main() {
	int t; scanf("%d", &t);
	while( t-- ) solve();
}

codeforces - 1276D:考虑成如果边 e 选择了 x,则删除 x 相邻的所有边。定义 dp[0][x] - (x, fa[x]) 在 x 处被删除,或选择了 x;dp[1][x] - (x, fa[x]) 不能在 x 处被删除,也不能选择 x;dp[2][x] - (x, fa[x]) 将会在 fa[x] 处被删除,不会造成影响。转移时要么有一条边选中 x,要么所有边都不选中 x。时间复杂度 O(n)。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 200000;
const int MOD = 998244353;

typedef pair<int, int> pii;
#define fi first
#define se second
#define mp make_pair

vector<pii>G[MAXN + 5];
void addedge(int u, int v, int w) {
	G[u].push_back(mp(w, v));
	G[v].push_back(mp(w, u));
}

int dp[3][MAXN + 5], pro[MAXN + 5];
void dfs(int x, int f) {
	for(int i=0;i<(int)G[x].size();i++)
		if( G[x][i].se != f )
			dfs(G[x][i].se, x);
	int lst = 1, tmp = 0;
	for(int i=0;i<(int)G[x].size();i++) {
		if( G[x][i].se == f ) {
			tmp = lst;
			continue;
		}
		pro[G[x][i].se] = lst, lst = 1LL*lst*dp[0][G[x][i].se]%MOD;
	}
	dp[1][x] = dp[2][x] = lst, lst = 1;
	bool flag = false;
	for(int i=(int)G[x].size()-1;i>=0;i--) {
		if( G[x][i].se == f ) {
			dp[0][x] = (dp[0][x] + 1LL*lst*tmp%MOD)%MOD;
			flag = true;
		}
		else {
			if( flag ) {
				dp[0][x] = (dp[0][x] + 1LL*lst*pro[G[x][i].se]%MOD*dp[1][G[x][i].se]%MOD)%MOD;
			}
			else {
				dp[1][x] = (dp[1][x] + 1LL*lst*pro[G[x][i].se]%MOD*dp[1][G[x][i].se]%MOD)%MOD;
			}
			dp[2][x] = (dp[2][x] + 1LL*lst*pro[G[x][i].se]%MOD*dp[1][G[x][i].se]%MOD)%MOD;
			lst = 1LL*lst*dp[2][G[x][i].se]%MOD;
		}
	}
}
/*
dp[0][x] - (x, fa[x]) 在 x 处被删除,或选择了 x 
dp[1][x] - (x, fa[x]) 不能在 x 处被删除,也不能选择 x
dp[2][x] - (x, fa[x]) 将会在 fa[x] 处被删除,不会造成影响
*/
int main() {
	int n; scanf("%d", &n);
	for(int i=1;i<n;i++) {
		int u, v; scanf("%d%d", &u, &v);
		addedge(u, v, i);
	}
	dfs(1, 0), printf("%d
", dp[1][1]);
}

bzoj - 3924:显然答案在带权重心,求出带权重心后直接动态点分治可以求它要的东西。注意到每个点的度数 <= 20,所以可以直接在点分树上暴力往下找带权重心。你可以像我一样判断两个点离其他点的距离和(这个再用点分治查嘛)然后 O(nlog^3n) 靠信仰 AC(跑不满)

#include <cstdio>
#include <vector>
using namespace std;

typedef long long ll;

const int MAXN = 100000;

struct edge{
	int to, dis;
	edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v, int w) {
	edge *p = (++ecnt);
	p->to = v, p->dis = w, p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->dis = w, p->nxt = adj[v], adj[v] = p;
}

bool vis[MAXN + 5]; int siz[MAXN + 5], hvy[MAXN + 5];
int get_size(int x, int f) {
	siz[x] = 1;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( vis[p->to] || p->to == f ) continue;
		siz[x] += get_size(p->to, x);
	}
	return siz[x];
}
int get_G(int x, int f, int tot) {
	hvy[x] = tot - siz[x];
	int ret = -1;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( vis[p->to] || p->to == f ) continue;
		int tmp = get_G(p->to, x, tot);
		if( ret == -1 || hvy[ret] > hvy[tmp] )
			ret = tmp;
		hvy[x] = max(hvy[x], siz[p->to]);
	}
	if( ret == -1 || hvy[ret] > hvy[x] )
		ret = x;
	return ret;
}
ll dis[20][MAXN + 5];
void get_dis(int x, int f, ll d, int dep) {
	dis[dep][x] = d;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( vis[p->to] || p->to == f ) continue;
		get_dis(p->to, x, d + p->dis, dep);
	}
}
int fa[MAXN + 5], dep[MAXN + 5];
vector<int>ch[MAXN + 5], ch2[MAXN + 5];
void divide(int x, int d) {
	dep[x] = d, vis[x] = true;
	get_dis(x, 0, 0, d);
	for(edge *p=adj[x];p;p=p->nxt) {
		if( vis[p->to] ) continue;
		int G = get_G(p->to, 0, get_size(p->to, 0));
		fa[G] = x, ch[x].push_back(G), ch2[x].push_back(p->to);
		divide(G, d + 1);
	}
}
ll sum[MAXN + 5], cnt[MAXN + 5];
ll s2[MAXN + 5], c2[MAXN + 5];
void modify(int x, ll e) {
	int d = dep[x];
	sum[x] += dis[d][x]*e, cnt[x] += e;
	for(int i=x;fa[i];i=fa[i]) {
		int d = dep[fa[i]];
		s2[i] += dis[d][x]*e, sum[fa[i]] += dis[d][x]*e;
		c2[i] += e, cnt[fa[i]] += e;
	}
}
ll query(int x) {
	ll ans = sum[x];
	for(int i=x;fa[i];i=fa[i]) {
		int d = dep[fa[i]];
		ans += dis[d][x]*(cnt[fa[i]] - c2[i]) + (sum[fa[i]] - s2[i]);
	}
	return ans;
}
int G;
ll get() {
	int x = G;
	while( true ) {
		int to = -1; ll t = query(x);
		for(int i=0;i<ch[x].size();i++)
			if( query(ch2[x][i]) < t )
				to = ch[x][i];
		if( to == -1 )
			break;
		else x = to;
	}
	return query(x);
}
int main() {
	int n, Q; scanf("%d%d", &n, &Q);
	for(int i=1;i<n;i++) {
		int a, b, c; scanf("%d%d%d", &a, &b, &c);
		addedge(a, b, c);
	}
	G = get_G(1, 0, get_size(1, 0)), divide(G, 0);
	for(int i=1;i<=Q;i++) {
		int u, e; scanf("%d%d", &u, &e);
		modify(u, e), printf("%lld
", get());
	}
}

codechef - Jump mission:如果不看 Pi < Pj 只看 i < j 就是个裸的李超线段树维护斜率优化 dp。如果加上 Pi < Pj 且 i < j 外层套一个类二维偏序的分治即可。时间复杂度 O(nlog^2n) 有信仰就能过。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXH = 6*int(1E5);
const int MAXN = 300000;
const ll INF = (1LL << 60);

struct line{
	ll k, b;
	line(ll _k=0, ll _b=INF) : k(_k), b(_b) {}
	ll get(ll x) {return k * x + b;}
};

struct segtree{
	int ch[2][2*MAXH + 5], root, ncnt;
	line lne[2*MAXH + 5];
	int newnode() {
		ncnt++;
		ch[0][ncnt] = ch[1][ncnt] = 0;
		lne[ncnt] = line(0, INF);
		return ncnt;
	}
	void insert(int &x, int l, int r, line ln) {
		if( !x ) x = newnode();
		if( lne[x].get(l) > ln.get(l) )
			swap(ln, lne[x]);
		int m = (l + r) >> 1;
		if( lne[x].get(m) > ln.get(m) )
			swap(ln, lne[x]), insert(ch[0][x], l, m, ln);
		else if( lne[x].get(r) > ln.get(r) )
			insert(ch[1][x], m + 1, r, ln);
	}
	ll query(int x, int l, int r, int p) {
		if( l == r ) return lne[x].get(p);
		int m = (l + r) >> 1;
		if( p <= m ) return min(query(ch[0][x], l, m, p), lne[x].get(p));
		else return min(query(ch[1][x], m + 1, r, p), lne[x].get(p));
	}
	void clear() {ncnt = root = 0;}
}T;
struct node{
	ll dp;
	int P, Q, A, H;
}a[MAXN + 5], t[MAXN + 5];
bool cmpP(node a, node b) {return a.P < b.P;}
bool cmpQ(node a, node b) {return a.Q < b.Q;}
void solve(int l, int r) {
	if( l == r ) {
		a[l].dp += a[l].A + 1LL*a[l].H*a[l].H;
		return ;
	}
	int m = (l + r) >> 1;
	solve(l, m);
	sort(a + m + 1, a + r + 1, cmpP);
	int pl = l, pr = m + 1, pt = l;
	while( pl <= m && pr <= r ) {
		if( a[pl].P < a[pr].P )
			T.insert(T.root, 1, MAXH, line(-2*a[pl].H, 1LL*a[pl].H*a[pl].H + a[pl].dp)), pl++;
		else a[pr].dp = min(a[pr].dp, T.query(T.root, 1, MAXH, a[pr].H)), pr++;
	}
	while( pl <= m ) T.insert(T.root, 1, MAXH, line(-2*a[pl].H, 1LL*a[pl].H*a[pl].H + a[pl].dp)), pl++;
	while( pr <= r ) a[pr].dp = min(a[pr].dp, T.query(T.root, 1, MAXH, a[pr].H)), pr++;
	T.clear();
	sort(a + m + 1, a + r + 1, cmpQ);
	solve(m + 1, r);
	pl = l, pr = m + 1, pt = l;
	while( pl <= m && pr <= r ) {
		if( a[pl].P < a[pr].P )
			t[pt++] = a[pl++];
		else t[pt++] = a[pr++];
	}
	while( pl <= m ) t[pt++] = a[pl++];
	while( pr <= r ) t[pt++] = a[pr++];
	for(int i=l;i<=r;i++) a[i] = t[i];
}
int main() {
	int N; scanf("%d", &N);
	for(int i=1;i<=N;i++) scanf("%d", &a[i].P);
	for(int i=1;i<=N;i++) a[i].Q = i;
	for(int i=1;i<=N;i++) scanf("%d", &a[i].A);
	for(int i=1;i<=N;i++) scanf("%d", &a[i].H);
	for(int i=2;i<=N;i++) a[i].dp = INF;
	a[1].dp = -1LL * a[1].H * a[1].H;
	solve(1, N);
/*
	for(int i=1;i<=N;i++)
		printf("%d %d %lld
", a[i].Q, a[i].P, a[i].dp);
*/
	printf("%lld
", a[N].dp);
}

codeforces - 438E:记 (F(x)) 表示权值为 i 的二叉树个数的生成函数,记 (G(x) = sum_{iin S}x^i),有 (F(x) = F^2(x) imes G(x) + 1),解二次方程即可。注意通过常数项舍掉一个解,以及分母分子需要消掉公因子才能作多项式逆元。

#include <cstdio>
#include <algorithm>
using namespace std;

const int MOD = 998244353;
const int MAXN = 800000;

namespace poly{
	int w[20], iw[20], inv[MAXN + 5];
	int pow_mod(int b, int p) {
		int ret = 1;
		while( p ) {
			if( p & 1 ) ret = 1LL*ret*b%MOD;
			b = 1LL*b*b%MOD;
			p >>= 1;
		}
		return ret;
	}
	void init() {
		inv[1] = 1;
		for(int i=2;i<=MAXN;i++)
			inv[i] = (MOD - 1LL*inv[MOD%i]*(MOD/i)%MOD)%MOD;
		for(int i=0;i<20;i++) {
			w[i] = pow_mod(3, (MOD - 1)/(1 << i));
			iw[i] = pow_mod(w[i], MOD - 2);
		}
	}
	void ntt(int *A, int n, int type) {
		for(int i=0,j=0;i<n;i++) {
			if( i < j ) swap(A[i], A[j]);
			for(int k=(n>>1);(j^=k)<k;k>>=1);
		}
		for(int i=1;(1<<i)<=n;i++) {
			int s = (1 << i), t = (s >> 1);
			int u = (type == 1 ? w[i] : iw[i]);
			for(int j=0;j<n;j+=s) {
				for(int k=0,p=1;k<t;k++,p=1LL*p*u%MOD) {
					int x = A[j+k], y = A[j+k+t];
					A[j+k] = (x + 1LL*y*p%MOD)%MOD;
					A[j+k+t] = (x + MOD - 1LL*y*p%MOD)%MOD;
				}
			}
		}
		if( type == -1 ) {
			int iv = inv[n];
			for(int i=0;i<n;i++)
				A[i] = 1LL*A[i]*iv%MOD;
		}
	}
	int length(int n) {
		int len; for(len = 1; len < n; len <<= 1);
		return len;
	}
	int t1[MAXN + 5], t2[MAXN + 5];
	void copy(int *A, int *B, int nB, int len) {
		for(int i=0;i<nB;i++) A[i] = B[i];
		for(int i=nB;i<len;i++) A[i] = 0;
	}
	void pmul(int *A, int nA, int *B, int nB, int *C) {
		int len = length(nA + nB - 1);
		copy(t1, A, nA, len);
		copy(t2, B, nB, len);
		ntt(t1, len, 1), ntt(t2, len, 1);
		for(int i=0;i<len;i++)
			C[i] = 1LL*t1[i]*t2[i]%MOD;
		ntt(C, len, -1);
	}
	int t3[MAXN + 5], t4[MAXN + 5];
	void pinv(int *A, int *B, int n) {
		if( n == 1 ) {
			B[0] = pow_mod(A[0], MOD-2);
			return ;
		}
		int m = (n + 1) >> 1;
		pinv(A, B, m);
		int len = length(n << 1);
		copy(t3, A, n, len);
		copy(t4, B, m, len);
		ntt(t3, len, 1), ntt(t4, len, 1);
		for(int i=0;i<len;i++)
			B[i] = 1LL*t4[i]*(2 + MOD - 1LL*t3[i]*t4[i]%MOD)%MOD;
		ntt(B, len, -1);
	}
	int t5[MAXN + 5], t6[MAXN + 5];
	void psqrt(int *A, int *B, int n) {
		if( n == 1 ) {
			B[0] = MOD - 1;
			return ;
		}
		int m = (n + 1) >> 1;
		psqrt(A, B, m);
		int len = length(n << 1);
		copy(t5, B, m, len);
		pinv(t5, t6, n);
		copy(t5, A, n, len);
		pmul(t5, n, t6, n, t6);
		copy(t5, B, m, n);
		for(int i=0;i<n;i++)
			B[i] = 1LL*inv[2]*(t5[i] + t6[i])%MOD;
	}
}

int f[MAXN + 5], g[MAXN + 5], t1[MAXN + 5], t2[MAXN + 5], t3[MAXN + 5];
int main() {
	int n, m, c, mn = MAXN; scanf("%d%d", &n, &m);
	for(int i=1;i<=n;i++)
		scanf("%d", &c), mn = min(mn, c), g[c]++;
	m = m + mn + 1;
	poly::init();
	for(int i=1;i<m;i++)
		t3[i] = (MOD - 4LL*g[i]%MOD) % MOD;
	t3[0] = 1;
	poly::psqrt(t3, t2, m);
	for(int i=mn;i<m;i++)
		t2[i-mn] = t2[i];
	for(int i=mn;i<m;i++)
		t3[i-mn] = 2LL*g[i]%MOD;
	poly::pinv(t3, t1, m);
	poly::pmul(t1, m, t2, m, f);
	for(int i=1;i<m-mn;i++)
		printf("%d
", f[i]);
}

atcoder - ARC062F:一眼 polya。点双分解,分三种情况贡献。边数<点数(一条边);边数=点数(一个简单环);边数>点数的点双。前两个的循环数量很好求,最后一个可以证明所有置换都是可行置换(手玩一下就证出来了)。然后把所有点双作背包。

#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 50;
const int MAXM = 100;
const int MOD = int(1E9) + 7;

int pow_mod(int b, int p) {
	int ret = 1;
	while( p ) {
		if( p & 1 ) ret = 1LL*ret*b%MOD;
		b = 1LL*b*b%MOD;
		p >>= 1;
	}
	return ret;
}

int N, M, K;

int s[MAXM + 5][MAXM + 5], fct[MAXM + 5];
void init() {
	s[0][0] = 1;
	for(int i=1;i<=MAXM;i++)
		for(int j=1;j<=i;j++)
			s[i][j] = (s[i-1][j-1] + 1LL*(i-1)*s[i-1][j]%MOD) % MOD;
	fct[0] = 1;
	for(int i=1;i<=MAXM;i++)
		fct[i] = 1LL*fct[i-1]*i%MOD;
}

struct edge{
	int to; edge *nxt;
}edges[2*MAXM + 5], *adj[MAXM + 5], *ecnt = edges;
void addedge(int u, int v) {
	edge *p = (++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->nxt = adj[v], adj[v] = p;
}

int f[2][MAXM + 5];
void copy() {
	for(int i=0;i<=M;i++)
		f[1][i] = f[0][i], f[0][i] = 0;
}
void update(int x, int k) {
	for(int i=x;i<=M;i++)
		f[0][i] = (f[0][i] + 1LL*f[1][i-x]*k%MOD)%MOD;
}

int gcd(int x, int y) {
	return y == 0 ? x : gcd(y, x % y);
}

int dfn[MAXM + 5], low[MAXM + 5], dcnt;
int stk[MAXM + 5], Gsiz, tp;
void dfs(int x, int fa) {
	dfn[x] = low[x] = (++dcnt);
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == fa ) continue;
		if( !dfn[p->to] ) {
			stk[++tp] = p->to, dfs(p->to, x);
			if( low[p->to] >= dfn[x] ) {
				int cnt = 0, tot = 0;
				do {
					if( stk[tp] == -1 ) cnt++;
					tot++;
				}while( stk[tp--] != p->to );
				copy();
				if( cnt == 0 ) update(1, 1);
				else if( cnt == 1 ) {
					for(int i=1;i<=tot;i++)
						update(gcd(tot, i), 1);
					Gsiz = 1LL*Gsiz*tot%MOD;
				}
				else {
					for(int i=1;i<=tot;i++)
						update(i, s[tot][i]);
					Gsiz = 1LL*Gsiz*fct[tot]%MOD;
				}
			}
			low[x] = min(low[x], low[p->to]);
		}
		else {
			low[x] = min(low[x], dfn[p->to]);
			if( dfn[p->to] < dfn[x] )
				stk[++tp] = -1;
		}
	}
}
int main() {
	init(), scanf("%d%d%d", &N, &M, &K);
	for(int i=1;i<=M;i++) {
		int u, v; scanf("%d%d", &u, &v);
		addedge(u, v);
	}
	Gsiz = f[0][0] = 1;
	for(int i=1;i<=N;i++)
		if( !dfn[i] ) dfs(i, 0);
	int ans = 0;
	for(int i=0,t=1;i<=M;i++,t=1LL*t*K%MOD)
		ans = (ans + 1LL*t*f[0][i]%MOD) % MOD;
	printf("%lld
", 1LL*ans*pow_mod(Gsiz, MOD-2)%MOD);
}

uoj - 422:一眼 min-max 容斥。发现 n 很小所以对 n 那一维状压,状态里面再存一个“有多少相邻的格子里面至少有一个喜欢的”。时间复杂度 O(4^n*m*n*m) 但是跑不满。理论上可以把 O(4^n) 优化成 O(3^n)。

#include <cstdio>

const int MOD = 998244353;
void add(int &x, int y) {
	x = (x + y) % MOD;
}

int inv[1200], g[1<<6], h[1<<6], b[1<<6];

char str[6][100];
int n, m, t, a[100];
void init() {
	inv[1] = 1;
	for(int i=2;i<=t;i++)
		inv[i] = MOD - 1LL*(MOD/i)*inv[MOD%i]%MOD;
	g[0] = 1;
	for(int i=1;i<(1<<n);i++) {
		if( i & 1 ) g[i] = (g[i >> 1] == 1 ? MOD - 1 : 1);
		else g[i] = g[i >> 1];
		b[i] = b[i >> 1] + (i & 1);
	}
	for(int i=0;i<(1<<n);i++) {
		for(int j=1;j<n;j++)
			h[i] += ((i & (1 << (j - 1))) || (i & (1 << j)));
	}
}


int f[2][1<<6][1200];
int main() {
	scanf("%d%d", &n, &m), t = (n-1)*m + n*(m-1), init();
	for(int i=0;i<n;i++)
		scanf("%s", str[i]);
	for(int j=0;j<m;j++)
		for(int i=0;i<n;i++)
			if( str[i][j] == '*' )
				a[j] |= (1 << i);
	int s1, s2; s1 = s2 = a[0];
	do {
		add(f[0][s2][h[s2]], g[s2]);
		
		if( s2 == 0 ) break;
		s2 = s1 & (s2 - 1);
	}while( true );
	for(int j=1;j<m;j++) {
		int lim = (j - 1)*n + j*(n - 1);
		
		int s3 = a[j-1], s4 = a[j-1];
		do {
			for(int i=0;i<=lim;i++)
				f[1][s4][i] = f[0][s4][i], f[0][s4][i] = 0;
			
			if( s4 == 0 ) break;
			s4 = s3 & (s4 - 1);
		}while( true );
		
		s1 = a[j], s3 = s4 = a[j-1];
		do {
			s2 = a[j];
			do {
				int p = b[s2 | s4] + h[s2];
				for(int i=0;i<=lim;i++)
					add(f[0][s2][i + p], 1LL*f[1][s4][i]*g[s2]%MOD);
				
				if( s2 == 0 ) break;
				s2 = s1 & (s2 - 1);
			}while( true );
			
			if( s4 == 0 ) break;
			s4 = s3 & (s4 - 1);
		}while( true );
	}
	int ans = 0; s1 = s2 = a[m-1];
	do {
		for(int j=1;j<=t;j++)
			ans = (ans + 1LL*f[0][s2][j]*inv[j]%MOD) % MOD;
		if( s2 == 0 ) break;
		s2 = s1 & (s2 - 1);
	}while( true );
	printf("%lld
", 1LL*ans*(MOD-1)%MOD*t%MOD);
}

atcoder - ARC093F:1 号人会打 N 场比赛,第 i 场比赛对手是某 2^i 个人中的最小值。容斥,枚举哪些 Ai 是最小值,从大到小枚举 Ai 考虑贡献。可用状压 dp 代替容斥。最后不要忘记乘个系数得到排列总数。

#include <cstdio>

const int MAXN = (1<<16);
const int MOD = int(1E9) + 7;

int pow_mod(int b, int p) {
	int ret = 1;
	while( p ) {
		if( p & 1 ) ret = 1LL*ret*b%MOD;
		b = 1LL*b*b%MOD;
		p >>= 1;
	}
	return ret;
}

int fct[MAXN + 5], ifct[MAXN + 5];
void init() {
	fct[0] = 1;
	for(int i=1;i<=MAXN;i++)
		fct[i] = 1LL*fct[i-1]*i%MOD;
	ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
	for(int i=MAXN-1;i>=0;i--)
		ifct[i] = 1LL*ifct[i+1]*(i+1)%MOD;
}
int comb(int n, int m) {
	return 1LL*fct[n]*ifct[m]%MOD*ifct[n-m]%MOD;
}

int f[MAXN + 5], dp[MAXN + 5];
int lowbit(int x) {
	return x & -x;
}

int A[16], N, M, t;
int main() {
	init();
	scanf("%d%d", &N, &M), t = (1 << N);
	for(int i=0;i<M;i++)
		scanf("%d", &A[i]);
	dp[0] = f[0] = 1;
	for(int i=1;i<t;i++)
		f[i] = 1LL*comb(i, lowbit(i))*f[i^lowbit(i)]%MOD;
	for(int i=M-1;i>=0;i--) {
		for(int s=t-1;s>=0;s--) {
			int p = (t - A[i]) - s;
			for(int j=0;j<N;j++) {
				int q = (1 << j);
				if( !(s & q) && p >= q - 1 )
					dp[s|q] = (dp[s|q] + MOD - 1LL*dp[s]*comb(p,q-1)%MOD) % MOD;
			}
		}
	}
	int ans = 0;
	for(int i=0;i<t;i++) ans = (ans + 1LL*dp[i]*f[t-1-i]%MOD) % MOD;
	for(int i=0;i<N;i++) ans = 1LL*ans*fct[1 << i] % MOD;
	printf("%lld
", 1LL*ans*t%MOD);
}

bzoj - 4001(我真的只是为了练习如何推生成函数)不难写出结点数为 n 时树的方案数的生成函数 (F(x) = F^2(x)x + 1) (就是卡特兰数)与叶子总数生成函数 (G(x) = 2G(x)F(x)x + x)。解方程然后泰勒展开,注意 (1 imes 3 imes dots imes (2r-1) imes 2^r = frac{(2r)!}{r!})。答案为 (g_n/f_n)

#include <cstdio>

int main() {
	int n; scanf("%d", &n);
	printf("%.9f
", 1.0*n/(2*n - 1)*(n + 1)/2);
}

bzoj - 5306:枚举恰好为 S 的有多少个,写出生成函数式子:

[N! imes[x^N]sum_{i=0}^{M}(w_i imes (frac{x^S}{S!})^i imes C_{M}^{i} imes (e^x - frac{x^S}{S!})^{M-i}) ]

然后令 (p = frac{x^S}{S!}, q = e^x),拆开二项式,化简式子,然后卷积,然后没了。

#include <cstdio>
#include <algorithm>
using namespace std;

const int MOD = 1004535809;
const int MAXN = 10000000;
const int MAXM = 4*100000;
const int G = 3;

inline int mul(int a, int b) {return 1LL*a*b % MOD;}
inline int add(int a, int b) {return (a + b >= MOD ? a + b - MOD : a + b);}
inline int sub(int a, int b) {return (a - b < 0 ? a - b + MOD : a - b);}

int pow_mod(int b, int p) {
	int ret = 1;
	for(int i=p;i;i>>=1,b=mul(b,b))
		if( i & 1 ) ret = mul(ret,b);
	return ret;
}

int N, M, S, tmp = 1;

int w[20], iw[20], ifct[MAXN + 5];
void init() {
	for(int i=0;i<20;i++) {
		w[i] = pow_mod(G, (MOD - 1)/(1 << i));
		iw[i] = pow_mod(w[i], MOD - 2);
	}
	int fct = 1, lim = max(M - 1, max(N, S));
	for(register int i=1;i<=lim;i++) {
		fct = mul(fct, i);
		if( i == M - 1 ) tmp = mul(tmp, fct);
		if( i == N ) tmp = mul(tmp, fct);
	}
	ifct[lim] = pow_mod(fct, MOD - 2);
	for(register int i=lim-1;i>=0;i--) ifct[i] = mul(ifct[i+1], i+1);
}

int length(int n) {
	int len; for(len = 1; len < n; len <<= 1);
	return len;
}
void ntt(int *A, int n, int type) {
	for(int i=0,j=0;i<n;i++) {
		if( i < j ) swap(A[i], A[j]);
		for(int k=(n>>1);(j^=k)<k;k>>=1);
	}
	for(int i=1;(1<<i)<=n;i++) {
		int s = (1 << i), t = (s >> 1);
		int u = (type == 1 ? w[i] : iw[i]);
		for(int j=0;j<n;j+=s) {
			for(int k=0,p=1;k<t;k++,p=mul(p,u)) {
				int x = A[j+k], y = mul(A[j+k+t], p);
				A[j+k] = add(x, y), A[j+k+t] = sub(x, y);
			}
		}
	}
	if( type == -1 ) {
		int iv = pow_mod(n, MOD - 2);
		for(int i=0;i<n;i++)
			A[i] = mul(A[i], iv);
	}
}

int A[MAXM + 5], f[MAXM + 5], g[MAXM + 5];
void get() {
	for(int i=0;i<M;i++) {
		f[i] = mul(A[i], ifct[i]);
		g[i] = ((i & 1) ? MOD - ifct[i]: ifct[i]);
	}
	int len = length(M << 1);
	ntt(f, len, 1), ntt(g, len, 1);
	for(int i=0;i<len;i++)
		f[i] = mul(f[i], g[i]);
	ntt(f, len, -1);
}

int read() {
	int x = 0; char ch = getchar();
	while( ch > '9' || ch < '0' ) ch = getchar();
	while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
	return x;
}

int main() {
	N = read(), M = read() + 1, S = read(), init();
	for(int i=0;i<M;i++) A[i] = read();
	get();
	int ans = 0;
	for(int i=0;i<M;i++) {
		int j = M - i - 1;
		if( j <= N/S ) {
			int p = N - j*S;
			int a = mul(f[j], ifct[i]), b = pow_mod(ifct[S], j);
			int c = mul(pow_mod(i, p), ifct[p]);
			ans = add(ans, mul(a, mul(b, c)));
		}
	}
	printf("%d
", mul(tmp, ans));
}

Atcoder - ARC103F:注意到 Di 最大的一定是叶子,且往父亲走一步 D 的变化值为 (D - size[i]) + (size[i])。按 Di 从大到小递推,维护每个结点子树的 size,即可得到可能的树形态。注意还要 dfs 一遍判断根的 D 值是否合法。

#include <map>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 100000;

vector<int>G[MAXN + 5];
map<ll, int>mp;

int siz[MAXN + 5];

ll check(int x) {
	ll ret = 0;
	for(int i=0;i<G[x].size();i++) {
		int to = G[x][i];
		ret += check(to) + siz[to];
	}
	return ret;
}

int main() {
	int n; scanf("%d", &n);
	for(int i=1;i<=n;i++) {
		ll D; scanf("%lld", &D);
		mp[D] = i, siz[i] = 1;
	}
	map<ll, int>::reverse_iterator rt;
	for(map<ll, int>::reverse_iterator it=mp.rbegin();it!=mp.rend();it++) {
		if( siz[it->second] == n ) {
			rt = it;
			break;
		}
		int x = it->second;
		ll k = it->first + siz[x] - (n - siz[x]);
		if( k < it->first && mp.count(k) ) {
			int p = mp[k];
			siz[p] += siz[x];
			G[p].push_back(x);
		}
		else {
			puts("-1");
			return 0;
		}
	}
	if( check(rt->second) == rt->first ) {
		for(int i=1;i<=n;i++) {
			for(int j=0;j<G[i].size();j++)
				printf("%d %d
", i, G[i][j]);
		}
	}
	else puts("-1");
}

codechef - AMR16F:记 (P(x) = sum_{i=1}i^2x^i = frac{x^2 + x}{(1-x)^3}, Q(x) = sum_{i=1}6 imesfrac{i(i+1)}{2}x^i = frac{6x}{(1-x)^3}),则答案 (ans = [x^N]P^A(x)Q^B(x) = 6^Bfrac{x^{A+B}(x+1)^A}{(1-x)^{3A+3B}})。用广义二项式定理展开分子与分母,直接暴力乘求第 N 项(分母的系数需要递推着算)。

#include <cstdio>

const int MOD = 1234567891;
const int MAXN = 600000;

int pow_mod(int b, int p) {
	int ret = 1;
	for(int i=p;i;i>>=1,b=1LL*b*b%MOD)
		if( i & 1 ) ret = 1LL*ret*b%MOD;
	return ret;
}

int fct[MAXN + 5], ifct[MAXN + 5];
void init() {
	fct[0] = 1;
	for(int i=1;i<=MAXN;i++)
		fct[i] = 1LL*fct[i-1]*i%MOD;
	ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
	for(int i=MAXN-1;i>=0;i--)
		ifct[i] = 1LL*ifct[i+1]*(i+1)%MOD;
}
int comb(int n, int m) {
	return 1LL*fct[n]*ifct[n-m]%MOD*ifct[m]%MOD;
}

void solve() {
	int A, B, N; scanf("%d%d%d", &A, &B, &N);
	if( A + B <= N ) {
		N -= (A + B);
		int M = 3*(A + B) - 1;
		int del = 1LL*ifct[M]*pow_mod(6, B)%MOD;
		int ans = 0, tmp = 1, p = M + N;
		for(int i=p;i>p-M;i--) tmp = 1LL*tmp*i%MOD;
		for(int j=0;j<=N&&j<=A;j++) {
			ans = (ans + 1LL*comb(A, j)*tmp%MOD)%MOD;
			tmp = 1LL*tmp*pow_mod(p,MOD-2)%MOD*(p-M)%MOD; p--;
		}
		printf("%d
", 1LL*ans*del%MOD);
	}
	else puts("0");
}

int main() {
	init();
	int T; scanf("%d", &T);
	while( T-- ) solve();
}

codechef - CNTL:第一问随便写。第二问可以写出生成函数 (F(x) = sum_{i=0}frac{x^i}{(2i+1)!}, G(x) = sum_{i=0}frac{x^i}{(2i)!}),根据 N-K 的奇偶最终答案为 (N! imes[x^N]F^{K-1}(x)G(x))(N! imes[x^N]F^K(x))。注意到 (F(x) = sinh(x) = frac{e^x - e^{-x}}{2}, G(x) = cosh(x) = frac{e^x + e^{-x}}{2})(双曲函数),直接二项式展开即可。

#include <cstdio>

const int MOD = int(1E9) + 7;
const int MAXN = 100000;

int pow_mod(int b, int p) {
	int ret = 1;
	for(int i=p;i;i>>=1,b=1LL*b*b%MOD)
		if( i & 1 ) ret = 1LL*ret*b%MOD;
	return ret;
}

int fct[MAXN + 5], ifct[MAXN + 5];
void init() {
	fct[0] = 1;
	for(int i=1;i<=MAXN;i++)
		fct[i] = 1LL*fct[i-1]*i%MOD;
	ifct[MAXN] = pow_mod(fct[MAXN], MOD-2);
	for(int i=MAXN-1;i>=0;i--)
		ifct[i] = 1LL*ifct[i+1]*(i+1)%MOD;
}
int comb(int n, int m) {
	return 1LL*fct[n]*ifct[m]%MOD*ifct[n-m]%MOD;
}

void solve() {
	int N, K; scanf("%d%d", &N, &K);
	int ans = 0;
	if( (N - K) & 1 ) {
		for(int i=0;i<=K-1;i++) {
			int j = K - 1 - i, k = (i + MOD - j)%MOD;
			int del = ((j & 1) ? MOD - comb(K-1, i): comb(K-1, i)) % MOD;
			ans = (ans + 1LL*del*pow_mod(k + 1, N))%MOD;
			ans = (ans + 1LL*del*pow_mod((k + MOD - 1)%MOD, N))%MOD;
		}
	}
	else {
		for(int i=0;i<=K;i++) {
			int j = K - i, k = (i + MOD - j)%MOD;
			int del = ((j & 1) ? MOD - comb(K, i): comb(K, i)) % MOD;
			ans = (ans + 1LL*del*pow_mod(k, N))%MOD;
		}
	}
	printf("%d ", (pow_mod(2, K) + MOD - 1 - ((N - K)&1))%MOD);
	printf("%d
", 1LL*ans*pow_mod(2, MOD-1-K)%MOD);
}

int main() {
	init();
	int T; scanf("%d", &T);
	while( T-- ) solve();
}
原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12020918.html