DP

CF1733D2

首先只需要 dp \(s[i] != t[i]\) 的位置。

可以缩起来,设 \(v[i]\) 表示 \(i\) 的原始位置。

\(f[i][j]\) 可以由 \(f[i+2][j], f[i][j-2], f[i+1][j-1]\) 转移过来。

因为一次操作删掉两个位置的字符。转移两个单位长度。

设 $w(i,j) = $ 一次删除 \(i,j\) 位置的答案。

\(v[j] - v[i] = 1\), 即原位置响临,那么代价为 \(\min(x, 2 \cdot y)\),直接删除或者搞到另一位置删除。

否则为 \(\min(x\cdot(v[j] - v[i]), y)\), 表示这段区间接连取反,或者直接搞。

也可以一段区间直接删,\(f[i][j] = y\cdot l / 2\), 意思是肯定有方案使得每次搞俩位置相离的。(x >= y 时的决策)。

比如 1 2 3 4 , 每次取 \((2,4)\) \((1,3)\)

点击查看代码
const int MAX = 5005, INF = 1e14;
char s[MAX], t[MAX];
vector<int>v;
int T, n, a[MAX], b[MAX], f[MAX][MAX];
void cmin(int &x,int y){
	x = min(x,y);
} 
int x,y;
inline int cal(int i,int j) {
	if(j - i == 1) {
		return min(x, y * 2);
	}else {
		return min(x * (j - i), y);
	}
}
signed main() {
	T = read();
	while(T--) {
		n = read();
		x = read(), y = read();
		scanf("%s",s+1);
		scanf("%s",t+1);
		int tmp = 0;
		
		vector<int>().swap(v); 
		for(int i=1;i<=n;++i) {
			if(s[i] ^ t[i]) v.ep(i);
		}
		if(v.size() & 1) {
			puts("-1");
			continue;
		}
		for(int l=2;l<=v.size();l+=2) {
			for(int i=0,j=i+l-1;j<v.size();++j,++i) {
				if(l == 2) {
					f[i][j] = cal(v[i], v[j]);
					continue;
				}
				f[i][j] = y * l / 2;
				cmin(f[i][j], f[i+2][j] + cal(v[i], v[i+1]));
				cmin(f[i][j], f[i][j-2] + cal(v[j-1], v[j]));
				cmin(f[i][j], f[i+1][j-1] + cal(v[i], v[j]));
			}
		}
		printf("%lld\n", f[0][v.size()-1]);
	}
	return 0;
}

AGC030D

题目链接

trick

方案数通过期望线性性转化。

考虑直接计算点对贡献。 答案即 \(2^q \sum_{i<j} P(A[i] > A[j])\)

\(f[q][i][j]\) 表示时刻 \(q\)\(A[i] > A[j]\) 概率。

设第 \(q\) 次搞了 \((x,y)\)

\[f[q][x][i] = f[q][y][i] = \dfrac{f[q-1][x][i] + f[q-1][y][i]}{2} \]

\[f[q][i][x] = f[q][i][y] = \dfrac{f[q-1][i][x] + f[q-1][i][y]}{2} \]

\[f[q][x][y] = f[q][y][x] = \dfrac{f[q-1][x][y] + f[q-1][y][x]}{2} \]

明显可以压掉第一维。这样一来每次转移 \(O(n)\) 个状态。时间复杂度 \(O(n(n+q))\)

点击查看代码
const int MAX = 3005,P=1e9+7;
int a[MAX],n,q,base=1,f[MAX][MAX];
int qpow(int x,int p){
	int ret=1;
	for(;p>0;p>>=1,x=1ll*x*x%P)if(p&1)ret=1ll*ret*x%P;
	return ret;
}
signed main() {
	n = read(); int q=read();
	for(int i=1;i<=n;++i) a[i] = read();
	int inv=(P+1)/2;
	for(int i=1;i<=n;++i) 
		for(int j=1;j<=n;++j)
			f[i][j] = a[i] > a[j];
	for(int i=1;i<=q;++i) {
		int x,y;
		x=read(),y=read(); 
		if(x ^ y) {
			int p;
			for(int j=1;j<=n;++j) {
				if(j == x || j == y) continue;
				p = (f[x][j] + f[y][j]) % P;
				f[x][j] = f[y][j] = 1ll * p * inv % P;
				p = (f[j][x] + f[j][y]) % P;
				f[j][x] = f[j][y] = 1ll * p * inv % P;
			}
			p = (f[x][y] + f[y][x]) % P;
			f[x][y] = f[y][x] = 1ll * inv * p % P;
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i) {
		for(int j=i+1;j<=n;++j) {
			ans=(ans+f[i][j])%P;
		}
	}
	ans=1ll*ans*qpow(2,q)%P;
	printf("%d\n",ans);
	return 0;
}


NOIP2017 宝藏

\(f[S][i][j]\) 表示 \(i\) 打通集合 \(S\) 中的点,深度为 \(j\) 的最小答案。

转移 \(f[S][i][j] = \min\limits_{T\sub S} f[T][k][j+1] + f[S/T][i][j] + w\cdot j\)

点击查看代码
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
#define int long long 
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define ep emplace_back
typedef long long ll;
typedef double db;
inline int read(){
	int x=0,v=1,ch=getchar();
	while('0'>ch||ch>'9') {if(ch=='-')v=0;ch=getchar();}
	while('0'<=ch&&ch<='9') {x=x*10+(ch&15);ch=getchar();}
	return v?x:-x;
}

const int MAX = 1e5+5, INF = 0x3f3f3f3f;
int n,m, f[4097][13][13];
int g[13][13];
inline void cmin(int &x,int y) {
	x = min(x, y);
}

int dp(int S,int x,int k) {
	if(S == (S&-S)) {
		if(S==(1<<x-1)) return 0;
		return INF;
	}
	int &z = f[S][x][k];
	if(z != -1) return z;
	
	z = INF;
	for(int y=1;y<=n;++y) {
		if(g[x][y] == INF || (S >> y-1 & 1) == 0 ) continue;
		int w = g[x][y];
		for(int T=(S-1)&S;T;T=(T-1)&S) {
			if(T >> y - 1 & 1) {
				int a = dp(S ^ T, x, k), b = dp(T, y, k + 1), c = k * w;
				z = min(z, a + b + k * w);
			}
		}
	}
	
	return z;
}
signed main() {
	n = read(),m = read();
	for(int i=1;i<=n;++i) 
		for(int j=1;j<=n;++j) g[i][j] = INF;
		
	for(int i=1;i<=m;++i) {
		int a,b,c;
		a=read(),b=read(),c=read();
		g[a][b] = g[b][a] = min(g[a][b], c);
	}

	memset(f,-1,sizeof(f));
	
	int ans = INF;
	for(int i=1;i<=n;++i) {
		ans = min(ans, dp( (1<<n)-1, i, 1));
	}
	printf("%lld\n", ans);
	return 0;
}


树形dp复杂度的感性证明?

大概就是说,两个点合并只会在 LCA 和并一次。所以是平方级别的复杂度。

P4655

学习了李超树。感觉 pyf 的写法十分厉害。

每个点维护一条直线,每次插入一条直线,比较 \(mid\) 的值,更优则交换。

查询时,把叶子到根路径的直线都比一次。

点击查看代码
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
#define int long long 
typedef long long ll;
#define ep emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define fin freopen("in.in","r",stdin);
inline int read() {
	int x=0, v=1,ch=getchar();
	while('0'>ch||ch>'9') {
		if(ch=='-') v=0;
		ch=getchar();
	}while('0'<=ch&&ch<='9') {
		x=(x*10)+(ch^'0');
		ch=getchar();
	}return v?x:-x;
}
const int M=1e6+5, N=1e5+5;
int tr[M<<2];
int n, f[N], h[N], w[N], b[N], a[N];
 
int g(int x,int k) { return b[k] + a[k] * x; }
void mdf(int x,int l,int r,int t) {
	if(l==r) {
		if(g(l,t)<g(l,tr[x]))tr[x]=t; 
		return ;
	} int mid=l+r>>1;
	if(g(mid,t)<g(mid,tr[x]))swap(tr[x],t);
	if(g(l,t)<g(l,tr[x])) mdf(x<<1,l,mid,t);
	else if(g(r,t)<g(r,tr[x])) mdf(x<<1|1,mid+1,r,t);
} 
int ask(int x,int l,int r,int s) {
	if(l==r) return g(s,tr[x]);
	int mid=l+r>>1;
	return min(g(s,tr[x]), s<=mid?ask(x<<1,l,mid,s):ask(x<<1|1,mid+1,r,s)); 
}
signed main() {
	n = read();
	b[0] = 1e18; 
	for(int i=1;i<=n;++i) h[i]=read();
	for(int i=1;i<=n;++i) w[i]=read(), w[i] += w[i-1];
	
	f[1]=0, a[1]=-2ll*h[1], b[1]=f[1]+1ll*h[1]*h[1]-w[1];
	mdf(1,0,1e6,1);
	for(int i=2;i<=n;++i) {
		f[i] = h[i] * h[i] + w[i-1] + ask(1,0,1e6,h[i]);
		a[i] = -2ll * h[i];
		b[i] = f[i] + 1ll * h[i] * h[i] - w[i];
		mdf(1,0,1e6,i);
	}
	cout << f[n] << endl;
	return 0;
}

CF319C

注意到 \(b[n] = 0\),且单调下降。

那么如果你砍了 \(b[n]\) 那就赢麻了。

然后就是求砍掉 \(b[n]\) 的最小代价。由单调下降得到,我们可以一路狂砍,就一直往前砍,以为回头看是不会减小代价的。

那么 \(f[i] = \min f[j] + a[i] \times b[j]\)。 枚举结尾砍了啥。

斜率优化即可。

posted @ 2022-10-25 18:44  Lates  阅读(331)  评论(0)    收藏  举报