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)\)。
明显可以压掉第一维。这样一来每次转移 \(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]\)。 枚举结尾砍了啥。
斜率优化即可。