@一句话题解 - 2019.12@
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\n", 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\n", 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\n", 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\n", a[i].Q, a[i].P, a[i].dp);
*/
printf("%lld\n", a[N].dp);
}
codeforces - 438E:记 \(F(x)\) 表示权值为 i 的二叉树个数的生成函数,记 \(G(x) = \sum_{i\in S}x^i\),有 \(F(x) = F^2(x)\times 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\n", 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\n", 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\n", 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\n", 1LL*ans*t%MOD);
}
bzoj - 4001:(我真的只是为了练习如何推生成函数)不难写出结点数为 n 时树的方案数的生成函数 \(F(x) = F^2(x)x + 1\) (就是卡特兰数)与叶子总数生成函数 \(G(x) = 2G(x)F(x)x + x\)。解方程然后泰勒展开,注意 \(1\times 3\times \dots \times (2r-1)\times 2^r = \frac{(2r)!}{r!}\)。答案为 \(g_n/f_n\)。
#include <cstdio>
int main() {
int n; scanf("%d", &n);
printf("%.9f\n", 1.0*n/(2*n - 1)*(n + 1)/2);
}
bzoj - 5306:枚举恰好为 S 的有多少个,写出生成函数式子:
然后令 \(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\n", 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\n", 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\times\frac{i(i+1)}{2}x^i = \frac{6x}{(1-x)^3}\),则答案 \(ans = [x^N]P^A(x)Q^B(x) = 6^B\frac{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\n", 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!\times[x^N]F^{K-1}(x)G(x)\) 或 \(N!\times[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\n", 1LL*ans*pow_mod(2, MOD-1-K)%MOD);
}
int main() {
init();
int T; scanf("%d", &T);
while( T-- ) solve();
}