[ATCoder] dp contest 题解

[ATcoder] dp contest

(洛谷的链接 : Here)

A

对于第 i i i 个位置的最小花费 f i f_i fi , 可能由 i − 2 i-2 i2 i − 1 i-1 i1 转移而来

显然 , i − 1 , i − 2 i-1,i-2 i1,i2 的贡献也应该做到最小花费即 f i − 1 , f i − 2 f_{i-1},f_{i-2} fi1,fi2 , 即满足最优子结构

	f[1] = 0, f[2] = dis(1, 2);
	for(int i=3; i<=n; i++)
	{
		f[i] = min(f[i-1]+dis(i, i-1), f[i-2]+dis(i, i-2));
	}
	cout << f[n];

B

对于第 i i i 个位置的最小花费 f i f_i fi , 可能由 i − 1 i-1 i1 i − k i-k ik 转移而来

	f[1] = 0, f[2] = dis(1, 2);
	for(int i=3; i<=n; i++)
	{
		f[i] = inf;
		for(int j=max(1, i-k); j<i; j++)
		{
			f[i] = min(f[i], f[j]+dis(i, j));
		}
	}
	cout << f[n];

C

对于 i + 1 i+1 i+1 天 , 会受到 i i i 天选择活动影响 , 因此设计 f ( i , x ) f(i,x) f(i,x) 表示前 i i i 天 , 第 i i i 天选择 x x x 的最优方案

	f[0][0] = f[0][1] = f[0][2] = 0;
	for(int i=1; i<=n; i++)
	{
		f[i][0] = max(f[i-1][1] + a[i], f[i-1][2] + a[i]); 
		f[i][1] = max(f[i-1][0] + b[i], f[i-1][2] + b[i]);
		f[i][2] = max(f[i-1][0] + c[i], f[i-1][1] + c[i]);
	}
	cout << max3(f[n][0], f[n][1], f[n][2]);

D 背包

对于某一步的决策 , 会受到上一步决策选了哪些物品 , 用了多少容量的影响

f i , j f_{i,j} fi,j 表示前 i i i 个物品容量不超过 j j j 的最优方案

	for(int i=0; i<=W; i++) f[0][i] = 0;
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=W; j++)
		{
			f[i][j] = f[i-1][j];
			if(w[i] <= j)	
				f[i][j] = max(f[i][j], f[i-1][j-w[i]]+v[i]);
		}
	}
	cout << f[n][W];

考虑到 f i , j f_{i,j} fi,j 的转移仅受到 f i − 1 , j f_{i-1,j} fi1,j 的影响 , 因此有一种优化空间的方法 :

	for(int i=1; i<=n; i++)
	{
		for(int j=W; j>=w[i]; j--)
		{
			f[j] = max(f[j], f[j-w[i]]+v[i]);
		}
	}
	cout << f[j];

原因 : 在执行 j j j 的循环之前 , f [ j ] f[j] f[j] 存的是 f i − 1 , j f_{i-1,j} fi1,j , 执行 j j j 循环即将 f [ j ] f[j] f[j] 均更新为 f i , j f_{i,j} fi,j

倒序执行可以保证 f [ j − w [ i ] ] f[j-w[i]] f[jw[i]] 表示的是上一轮即 f i − 1 , j − w [ i ] f_{i-1,j-w[i]} fi1,jw[i] . 同时对于 f < w [ i ] ff<w[i] 的 , f [ j ] f[j] f[j] 保留 f i − 1 , j f_{i-1,j} fi1,j 的值

E 背包

与 D 不同 , 此题 W W W 的范围很大 , 但考虑到 v v v 的范围较小 , 我们可以设计 :

f i , j f_{i,j} fi,j 表示前 i i i 个物品 , 得到 j j j 收益占据的最少的空间

	for(int i=1; i<=n; i++)
	{
		cin >> w[i] >> v[i];
		V += v[i];
	}
	for(int i=0; i<=V; i++) f[0][i] = inf;
	f[0][0] = 0;
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=V; j++)
		{
			f[i][j] = f[i-1][j];
			if(v[i] <= j)	
				f[i][j] = min(f[i][j], f[i-1][j-v[i]]+w[i]);
		}
	}
	int ans = 0;
	for(int j=0; j<=V; j++)
	{
		if(f[n][j] <= W) ans = max(ans, j);
	}
	cout << ans;

F LCS

先考虑求出 LCS 长度 : 对于两个字符串的匹配 , 受两字符串各自匹配到的位置影响 , 状态设计为 : f i , j f_{i,j} fi,j 表示 A A A 串前 i i i 位 , B B B 串前 j j j 的 LCS 长度

	int n = strlen(a+1), m = strlen(b+1);
	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=m; j++)
		{
			if(a[i] == b[j]) f[i][j] = f[i-1][j-1] + 1;
			else f[i][j] = max(f[i-1][j], f[i][j-1]);
		}
	}
	cout << f[n][m];

记录转移方式即可

void print(int i,int j)
{
	if(i==0 || j==0) return ;
	if(pre[i][j]==3) print(i-1,j-1), cout<<a[i];
	if(pre[i][j]==2) print(i,j-1);
	if(pre[i][j]==1) print(i-1,j);
}
int main()
{
	scanf("%s%s",a+1,b+1);
	n=strlen(a+1), m=strlen(b+1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1, pre[i][j]=3;
			else
			{
				if(f[i-1][j]>f[i][j-1]) f[i][j]=f[i-1][j], pre[i][j]=1;
				else f[i][j]=f[i][j-1] pre[i][j]=2;
			}
		}
	}
	print(n,m);
}

G 图的dp

DAG 上的动态规划 : 受到已经访问的点和上一步走到的点影响

Sol1 : 记忆化搜索

int dfs(int u)
{
	if(vis[u]) return f[u];
	vis[u] = 1;
	int res = 0;
	for(auto v : G[u]) 
		res = max(res, dfs(v)+1);
	return f[u] = res;
}
int main()
{
	cin >> n >> m; 
	for(int i=1; i<=m; i++)
	{
		int u, v;
		cin>>u>>v;
		G[u].push_back(v);
	}
	for(int i=1; i<=n; i++)
		if(!vis[i]) dfs(i);
	int ans = 0;
	for(int i=1; i<=n; i++)
		ans = max(ans, f[i]);
	cout << ans;
}

Sol2 : 拓扑排序

	for(int i=1; i<=n; i++)
	{
		if(indeg[i] == 0) q.push(i);
	}
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(auto v : G[u])
		{
			f[v] = max(f[v], f[u]+1);
			indeg[v]--;
			if(indeg[v] == 0) q.push(v);
		}
	}
	int ans = 0;
	for(int i=1; i<=n; i++)
		ans = max(ans, f[i]);
	cout << ans;

H 计数dp

计数递推

	f[1][1] = 1;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			if(!(i==1 && j==1) and a[i][j] == '.')
					f[i][j] = f[i][j-1] + f[i-1][j], f[i][j]%=mod;
	cout << f[n][m];

I 概率dp

概率 dp , 某一状态受之前状态影响 , 也会受到当前选第 i i i 个影响概率 . 设计 f i , j f_{i,j} fi,j 表示前 i i i j j j 个正面 的概率

	f[0][0] = 1;
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=i; j++)
        {
            f[i][j] += (1-p[i]) * f[i-1][j];
            if(j) f[i][j] += p[i] * f[i-1][j-1];
        }
    }
    double sum = 0;
    for(int i=n; i*2>n; i--)
    {
        sum += f[n][i];
    }
    cout << sum;

J 期望dp

期望 dp , 考虑 f ( i , j , k ) f(i,j,k) f(i,j,k) 表示 i i i 1 1 1 , j j j 2 2 2 , k k k 3 3 3 还需要操作次数的期望

那么有 n − i − j − k n \frac{n-i-j-k}{n} nnijk 的概率变为 f ( i , j , k ) f(i,j,k) f(i,j,k) , i n \frac{i}{n} ni 的概率 f ( i − 1 , j , k ) f(i-1,j,k) f(i1,j,k) , j n \frac{j}{n} nj 的概率 f ( i + 1 , j − 1 , k ) f(i+1,j-1,k) f(i+1,j1,k) , k n \frac{k}{n} nk 的概率 f ( i , j + 1 , k − 1 ) f(i,j+1,k-1) f(i,j+1,k1)

化简得 f ( i , j , k ) = i i + j + k f ( i − 1 , j , k ) + j i + j + k f ( i + 1 , j − 1 , k ) + k i + j + k f ( i , j + 1 , k − 1 ) + n i + j + k f(i,j,k)=\frac{i}{i+j+k}f(i-1,j,k)+\frac{j}{i+j+k}f(i+1,j-1,k)+\frac{k}{i+j+k}f(i,j+1,k-1)+\frac{n}{i+j+k} f(i,j,k)=i+j+kif(i1,j,k)+i+j+kjf(i+1,j1,k)+i+j+kkf(i,j+1,k1)+i+j+kn

可以采用记忆化搜索的方式

double dfs(int i, int j, int k)
{
	if(i==0 and j==0 and k==0) return 0;
	if(mem[i][j][k]) return f[i][j][k];
	mem[i][j][k] = true;
	double res = 0;
	if(i) res += dfs(i-1, j, k) * i/(i+j+k) ;
	if(j) res += dfs(i+1, j-1, k) * j/(i+j+k);
	if(k) res += dfs(i, j+1, k-1) * k/(i+j+k);
	res += 1.0*n/(i+j+k);
	return f[i][j][k] = res;
}
int main()
{
	cin >> n;
	for(int i=1; i<=n; i++)
	{
		cin >> a[i], t[a[i]]++;
	}
	f[0][0][0] = 0;
	printf("%.15lf", dfs(t[1], t[2], t[3])); 
	return 0;
}


采用递推方式时要注意 :

  1. 顺序 : k k k 的转移需要已知 k − 1 k-1 k1 , 因此优先正序枚举 k k k , j j j 的转移需要知道 k k k j − 1 j-1 j1 k − 1 k-1 k1 j + 1 j+1 j+1 , 后者已知 , 因此正序枚举 j j j , i i i 转移同理正序枚举 i i i

  2. 范围 : 转移时可能由超过 t t t 范围的状态转移而来 , 因此需要枚举到 n n n

int main()
{
	cin >> n;
	for(int i=1; i<=n; i++)
	{
		cin >> a[i], t[a[i]]++;
	}
	f[0][0][0] = 0;
	for(int k=0; k<=n; k++)
	{
		for(int j=0; j<=n; j++)
		{
			for(int i=0; i<=n; i++)
			{
				if(i) f[i][j][k] += f[i-1][j][k] * i/(i+j+k);
				if(j) f[i][j][k] += f[i+1][j-1][k] * j/(i+j+k);
				if(k) f[i][j][k] += f[i][j+1][k-1] * k/(i+j+k);
				if(i or j or k) f[i][j][k] += 1.0*n/(i+j+k);
			}
		}
	}
	printf("%.15lf", f[t[1]][t[2]][t[3]]); 
	return 0;
}

K 博弈论dp

博弈论

记忆化

bool dfs(int x)
{
	if(mem[x]) return f[x];
	mem[x] = true;
	for(int i=1; i<=n; i++)
	{
		if(x >= a[i]) f[x] |= (!dfs(x-a[i]));
	}
	return f[x];
}
int main()
{
	...
	if(dfs(k)) puts("First");
	else puts("Second");
	return 0;
}

递推

	for(int i=0; i<=k; i++)
	{
		for(int j=1; j<=n; j++)
		{
			if(i >= a[j]) f[i] |= (!f[i-a[j]]);
		}
	}
	if(f[k]) puts("First");
	else puts("Second");

L 区间dp

f l , r f_{l,r} fl,r 表示当前人在 [ l , r ] [l,r] [l,r] 区间能取到的最优答案

int dfs(int l, int r, bool op)
{
	if(l > r) return 0;
	if(mem[l][r]) return f[l][r];
	mem[l][r] = true;
	if(op) 
		return f[l][r] = max(dfs(l, r-1, 0) + a[r], dfs(l+1, r, 0) + a[l]);
	else 
		return f[l][r] = min(dfs(l, r-1, 1) - a[r], dfs(l+1, r, 1) - a[l]);
}
...
	cout << dfs(1, n, 1);
	return 0;


M 前缀和优化dp

f i , j f_{i,j} fi,j 为前 i i i 个小朋友 , 总数为 j j j 的分配方案数

那么 f i , j = ∑ 0 ≤ x ≤ a i f i − 1 , j − x f_{i,j}=\displaystyle\sum\limits_{0\le x \le a_i} f_{i-1,j-x} fi,j=0xaifi1,jx

总共有 O ( N ∗ K ) O(N*K) O(NK) 状态 , 每个状态需要转移的状态量是 O ( K ) O(K) O(K) , 这样做是 O ( N K 2 ) O(NK^2) O(NK2) 的 , 会超时

考虑到 f i , j f_{i,j} fi,j 的状态转移只依赖于 f i − 1 , j ′ ( j ′ ≤ j ) f_{i-1,j'}(j'\le j) fi1,j(jj) 的状态的 ∑ \sum , 考虑使用前缀和

s u m i , j sum_{i,j} sumi,j 表示 ∑ 0 ≤ x ≤ j f i , x \displaystyle\sum\limits_{0\le x \le j} f_{i,x} 0xjfi,x

那么 f i , j = s u m i − 1 , j − s u m i − 1 , j − a i − 1 f_{i,j}=sum_{i-1,j}-sum_{i-1,j-a_i-1} fi,j=sumi1,jsumi1,jai1

s u m i , j = s u m i , j − 1 + f i , j sum_{i,j}=sum_{i,j-1}+f_{i,j} sumi,j=sumi,j1+fi,j

此时转移复杂度降到了 O ( 1 ) O(1) O(1) , 总时间复杂度 O ( N K ) O(NK) O(NK)

	f[1][0] = sum[1][0] = 1;
	for(int j=1; j<=k; j++) 
	{
		f[1][j] = (j<=a[1]);
		sum[1][j] = sum[1][j-1] + f[1][j];
	}
	for(int i=2; i<=n; i++)
	{
		sum[i][0] = f[i][0] = 1;
		for(int j=1; j<=k; j++)
		{
			if(j > a[i]) f[i][j] = sum[i-1][j] - sum[i-1][j-a[i]-1];
			else f[i][j] = sum[i-1][j];
			sum[i][j]  = sum[i][j-1] + f[i][j];
		}
	} 
	cout << f[n][k]; 

N 区间dp

状态 : 一个区间的最优状态只会受其区间内数的影响 , 记 f l , r f_{l,r} fl,r [ l , r ] [l,r] [l,r] 的最优合并方案

转移 : 考虑按某一断点将 [ l , r ] [l,r] [l,r] 分为 [ l , k ] [l,k] [l,k] [ k + 1 ] [k+1] [k+1] , 此时将两堆合并代价为 ∑ l ≤ i ≤ r a i \sum\limits_{l\le i \le r} a_i lirai

因此 f l , r = min ⁡ { f l , k + f k + 1 , r + ∑ l ≤ i ≤ r a i } f_{l,r}=\min \lbrace f_{l,k} + f_{k+1,r} + \sum\limits_{l\le i \le r} a_i \rbrace fl,r=min{fl,k+fk+1,r+lirai}

int dfs(int l, int r)
{ 
	if(l == r) return 0;
	if(mem[l][r]) return f[l][r];
	mem[l][r] = true;
	int res = 1e18;
	for(int i=l; i<r; i++)
	{
		res = min (res, dfs(l,i) + dfs(i+1,r) + sum[r] - sum[l-1]);
	}
	return f[l][r] = res;
}

O 状压dp

状压dp 入门

int dfs(int i,int vis)
{
    if(i==n+1) return 1;
    if(mem[vis]) return f[vis];
    mem[vis]=true;
    int res=0;
    for(int j=1;j<=n;j++)
    {
        int mask= 1<<(j-1) ;
        if(vis & mask) continue;
        if(a[i][j] == 0) continue;
        res+=dfs(i+1, vis ^ mask), res%=mod;
    }
    return f[vis]= res;
}

P 树dp

一个节点的方案数会受自己的颜色以及父亲的颜色影响 , 因此我们可以按照 dfs 的某个顺序进行 dp , 叶子节点 白/黑 的方案数为 1 1 1 , 从下至上递推求解

void dfs(int u, int pre)
{
	f[u][0] = f[u][1] = 1;
	for(auto v : G[u])
	{
		if(v == pre) continue;
		dfs(v, u);
		f[u][0] *= f[v][0] + f[v][1], 			f[u][0] %= mod;
		f[u][1] *= f[v][0], 					f[u][1] %= mod;
	}
}

Q 树状数组优化dp

f i f_i fi 表示前 i i i 个 ( i i i 必选 ) 的最优答案。

那么 f i = max ⁡ j < i , h j < h i ( f j ) + a i f_i=\max\limits_{jfi=j<i,hj<himax(fj)+ai

即二维偏序问题 , 考虑用树状数组以 h h h 为下标维护 < h i <hi 的最大的 f f f

int lowbit(int x) {return x & (-x);}
void modify(int pos, int val)
{
	for(int i=pos; i<=n; i+=lowbit(i))
		tree[i] = max(tree[i], val);
}
int query(int pos)
{
	int res = 0;
	for(int i=pos; i>0; i-=lowbit(i))
		res = max(res, tree[i]);
	return res;
}
...
	for(int i=1; i<=n; i++)
	{
		f[i] = query(h[i]-1) + a[i];
		modify(h[i], f[i]);
	}	
	int ans = 0;
	for(int i=1; i<=n; i++)
		ans = max(ans, f[i]);
	cout << ans; 

R 矩阵加速dp

此题 K ≤ 1 0 18 K\le 10^{18} K1018 + 邻接矩阵, 猜测矩阵快速幂

f l [ i ] [ j ] f_l[i][j] fl[i][j] 表示长度为 i → j i\to j ij , 长度为 l l l 的路的方案数

那么 f l [ i ] [ j ] = ∑ ( f l − 1 [ i ] [ k ] × f 1 [ k ] [ j ] ) f_l[i][j]=\sum (f_{l-1}[i][k]\times f_1[k][j]) fl[i][j]=(fl1[i][k]×f1[k][j]) , 其中 f 1 [ k ] [ j ] f_1[k][j] f1[k][j] 为邻接矩阵

因此 F K = F K − 1 F 1 = F K − 2 F 1 2 = . . . = F 1 K F_K=F_{K-1}F_1=F_{K-2}F_1^2=...=F1^K FK=FK1F1=FK2F12=...=F1K

Matrix operator * (const Matrix &x, const Matrix &y)
{
	Matrix res;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
			for(int k=1; k<=n; k++)
				res.a[i][j] += x.a[i][k] * y.a[k][j], res.a[i][j] %= mod;
	return res; 
}
Matrix qpow(Matrix a, int b)
{
	Matrix base = a, res;
	for(int i=1; i<=n; i++) res.a[i][i] = 1;
	while(b)
	{
		if(b & 1) res = res * base;
		base = base * base;
		b >>= 1;
	}
	return res;
}
...
	Matrix res = qpow(A, k);
	int ans = 0;
	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=n; j++)
		{
			ans += res.a[i][j], ans %= mod;
		}
	}
	cout << ans;
	return 0;
}

S 数位dp

K ≤ 1 0 10000 K\le10^{10000} K1010000 , 显然数位dp

int dfs(int i, int sum, bool free)
{
	if(i==n+1) return sum == 0;
	if(mem[i][sum][free]) return f[i][sum][free];
	mem[i][sum][free] = true;
	int res = 0;
	for(int d=0; d<=9; d++)
	{
		if(!free and d > a[i]) break;
		res += dfs(i+1, (sum+d) % D, free | d<a[i]),  res %= mod;
	}
	return f[i][sum][free] = res;
}
...
	cout << (dfs(1, 0, 0) + mod - 1) % mod;
	return 0;
}

T

若设计 f i , j f_{i,j} fi,j 表示前 i i i 位,第 i i i 位填 j j j 的方案数,此时无法保证这一序列没有重复数字。

考虑设计 f i , j f_{i,j} fi,j 表示前 i i i 位,在前 i i i 位中 ,第 i i i 位为第 j j j 大的方案数,此时即避免了重复情况。

对于转移,若第 i − 1 i-1 i1 位小于第 i i i 位,则 f i , j = ∑ 1 ≤ k ≤ j − 1 f i − 1 , k f_{i,j}=\sum\limits_{1\le k \le j-1}f_{i-1,k} fi,j=1kj1fi1,k
若第 i − 1 i-1 i1 位大于第 i i i 位,此前第 j j j 大…第 i − 1 i-1 i1 大 将变为 第 j + 1 j+1 j+1 大…第 i i i 大,则 f i , j = ∑ j ≤ k ≤ i − 1 f i − 1 , k f_{i,j}=\sum\limits_{j\le k \le i-1}f_{i-1,k} fi,j=jki1fi1,k

f i , j = { ∑ 1 ≤ k ≤ j − 1 f i − 1 , k d i − 1 < d i ∑ j ≤ k ≤ i − 1 f i − 1 , k d i − 1 > d i f_{i,j}=\begin{cases} \sum\limits_{1\le k \le j-1}f_{i-1,k} & d_{i-1}d_i \end{cases} fi,j= 1kj1fi1,kjki1fi1,kdi1<didi1>di

此时状态数 O ( n 2 ) O(n^2) O(n2),转移复杂度 O ( n ) O(n) O(n) ,时间复杂度 O ( n 3 ) O(n^3) O(n3) ,会超时,考虑前缀和优化。

//version 1
signed main()
{
	cin >> n;
	for(int i=1; i<n; i++) cin >> s[i];
	f[1][1] = 1; sum[1][1] = 1;
	for(int i=2; i<=n; i++)
	{
		for(int j=1; j<=i; j++)
		{
			if(s[i-1] == '<') f[i][j] = sum[i-1][j-1];
			if(s[i-1] == '>') f[i][j] = sum[i-1][i-1] - sum[i-1][j-1],  	f[i][j] = (f[i][j] + mod) % mod;
			sum[i][j] = sum[i][j-1] + f[i][j], 	sum[i][j] %= mod;
		}
	}
	int ans = 0;
	for(int i=1; i<=n; i++) ans += f[n][i], ans %= mod;
	cout << ans; 
}

或者注意到
f i , j = { f i , j − 1 + f i − 1 , j − 1 d i − 1 < d i f i , j + 1 + f i − 1 , j d i − 1 > d i f_{i,j}=\begin{cases} f_{i,j-1}+f_{i-1,j-1} & d_{i-1}d_i \end{cases} fi,j= fi,j1+fi1,j1fi,j+1+fi1,jdi1<didi1>di

//version 2
signed main()
{
	cin >> n;
	for(int i=1; i<n; i++) cin >> s[i];
	f[1][1] = 1;
	for(int i=2; i<=n; i++)
	{
		if(s[i-1] == '<') 
			for(int j=1; j<=i; j++) 
				f[i][j] = f[i][j-1] + f[i-1][j-1],  f[i][j] %= mod;
		if(s[i-1] == '>') 
			for(int j=i; j>=1; j--) 
				f[i][j] = f[i][j+1] + f[i-1][j],  f[i][j] %= mod;
	}
	int ans = 0;
	for(int i=1; i<=n; i++) ans += f[n][i], ans %= mod;
	cout << ans; 
}

U SOS dp

首先, 我们 O ( n 2 ∗ 2 n ) O(n^2 *2^n) O(n22n) 预处理出所有状态 S S S , S S S 分为一组的收益 g [ S ] g[S] g[S]

我们记 f [ S ] f[S] f[S] 为划分 S S S 的最大收益, 那么有 f [ S ] = max ⁡ ( f [ S − x ] + g [ x ] ) f[S]=\max (f[S-x]+g[x]) f[S]=max(f[Sx]+g[x]) , 其中 x x x S S S 的子集

时间复杂度 O ( 3 n ) O(3^{n}) O(3n) (对于每只兔子 , 要么属于 x x x , 要么不属于 x x x 属于 S S S , 要么不属于 S S S)

//version 1
int dfs(int S)
{
    if(S==0) return 0;
    if(mem[S]) return f[S];
    mem[S] = true;
    int res = g[S];
    for(int x=0; x<S; x++)
        if((S | x) == S)
            res = max(res, dfs(S^x) + g[x]);
    return f[S] = res;
}
// main
{
    for(int i=0; i<n; i++)
        for(int S=0; S<(1<<n); S++)
            if(!(S & 1<<i))
            {
                g[S|1<<i] = g[S];
                for(int j=0; j<n; j++)
                    if(S & 1<<j) g[S|1<<i] += a[i][j];
            }
    cout << dfs((1<<n)-1);
}

对于枚举子集, 还存在一个剪枝

for(int x=0; x
优化为 for(int x=S; x>0; x = (x-1)&S)

//version 2
int dfs(int S)
{
    if(S==0) return 0;
    if(mem[S]) return f[S];
    mem[S] = true;
    int res = g[S];
    for(int x=S; x>0; x = (x-1)&S)
        if((S | x) == S)
            res = max(res, dfs(S^x) + g[x]);
    return f[S] = res;
}
// main()
{
     ...
}

对比:

在这里插入图片描述

Y 组合数+容斥

( 1 , 1 ) (1,1) (1,1) ( x , y ) (x,y) (x,y) 之间均没有障碍物,则从 ( 1 , 1 ) (1,1) (1,1) 走到 ( x , y ) (x,y) (x,y) 的方案数为 C x + y − 2 x − 1 C_{x+y-2}^{x-1} Cx+y2x1 。(在 x + y − 1 x+y-1 x+y1 步中选 x − 1 x-1 x1 步向下走)

无障碍物的方案数 减去 经过障碍的方案数 即为 合法方案数

对于处理 经过障碍的方案数 ,我们考虑容斥:经过一个障碍物的方案数减去经过两个障碍物的方案数加上经过三个障碍物的方案数 …

考虑将障碍物进行排序, f i = C x i + y i − 2 x i − 1 − ∑ 1 ≤ j < i f j × C ( x i − x j ) + ( y i − y j ) x i − x j \large f_i=C_{x_i+y_i-2}^{x_i-1}-\sum \limits_{1\le jfi=Cxi+yi2xi11j<ifj×C(xixj)+(yiyj)xixj

#include
#define int long long
using namespace std;
const int N = 1e6+5;
const int mod = 1e9+7;
int h, w, n, inv[N+5], fac[N+5], f[3005], x[3005], y[3005];
struct Node {
	int x, y;
}a[3005];
bool cmp(Node p, Node q) {return (p.x==q.x) ? (p.y<q.y) : (p.x<q.x);}
int C(int a, int b) { return ((fac[a] * inv[a-b]) % mod) * inv[b] % mod; }
signed main()
{
	cin >> h >> w >> n;
	for(int i=1; i<=n; i++) cin >> a[i].x >> a[i].y;
	fac[0] = 1LL;
	for(int i=1; i<=N; i++) fac[i] = fac[i-1] * i % mod;
	inv[0] = inv[1] = 1LL;
	for(int i=2; i<=N; i++) inv[i] = (mod - mod/i) * inv[mod%i] % mod;
	for(int i=2; i<=N; i++) inv[i] = inv[i-1] * inv[i] % mod;
	sort(a+1, a+n+1, cmp);
	for(int i=1; i<=n; i++) x[i] = a[i].x, y[i] = a[i].y;
	x[n+1] = h, y[n+1] = w; 
	for(int i=1; i<=n+1; i++)
	{
		f[i] = C(x[i] + y[i] - 2 , x[i] - 1);
		for(int j=1; j<i; j++)
		{
			if(x[j] <= x[i] && y[j] <= y[i])
				f[i] -= (f[j] * C(x[i] - x[j] + y[i] - y[j], x[i] - x[j])) % mod, f[i] %= mod, f[i] = (f[i] + mod) % mod; 
		}
	}
	cout << f[n+1]; 
	return 0;
}

你可能感兴趣的:(题解,动态规划,c++)