他的吻是一种毒 舌尖的温柔纠缠千万变数 梦境拒绝这场苏醒 我不愿失去你
test31
3-A 视频监控 (video.cpp)
横纵独立,分开处理,选择最大空隙中操作次数最小的,操作次数比较向上向下即可。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pb push_back
using namespace std;
const int N=100005;
int R, C, tot, n, m, x[N], y[N], lenx, leny, ansx, ansy;
signed main() {
// freopen("1.txt","r",stdin);
freopen("video.in","r",stdin);
freopen("video.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> R >> C >> tot;
up(i,1,tot) cin >> x[i] >> y[i];
sort(x+1,x+1+tot), n=unique(x+1,x+1+tot)-x-1;
sort(y+1,y+1+tot), m=unique(y+1,y+1+tot)-y-1;
lenx=x[1]-1+R-x[n], leny=y[1]-1+C-y[m];
up(i,2,n) {
int d=x[i]-x[i-1]-1, cnt=min(x[i-1],R-x[i]+1);
if(d>lenx||d==lenx&&cnt<ansx) lenx=d, ansx=cnt;
}
up(i,2,m) {
int d=y[i]-y[i-1]-1, cnt=min(y[i-1],C-y[i]+1);
if(d>leny||d==leny&&cnt<ansy) leny=d, ansy=cnt;
}
cout << (R-lenx)*(C-leny) << ' ' << ansx+ansy << '\n';
return 0;
}
3-B 卡牌游戏 (card.cpp)
我都懒得说了,枚举多拿几张 \(1\),然后暴力 dp 方案数,注意顺序的贡献要乘对,对 \(1\) 的处理考虑哪些集合可以给对手。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
using namespace std;
const int N=505, P=998244353;
int n, m, f[N][N], g[N][N], Ans, dp[N][N];
inline void add(int &a,int b) { a=(a+b)%P; }
signed main() {
// freopen("1.txt","r",stdin);
freopen("card.in","r",stdin);
freopen("card.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f[0][0]=g[1][0]=dp[0][0]=1;
up(i,1,m) up(j,0,i) {
if(j+1<=i) add(f[i][j+1],f[i-1][j]);
if(j-1>=0) add(f[i][j-1],f[i-1][j]);
}
up(i,2,n) up(j,0,m) up(k,0,j) add(g[i][j],g[i-1][j-k]*f[m][k]%P);
up(i,1,m) up(j,0,i) {
if((j+1)<=i-(j+1)) add(dp[i][j+1],dp[i-1][j]);
if(j<=i-j) add(dp[i][j],dp[i-1][j]);
}
up(x,0,m) if(x%2==0) add(Ans,dp[m][(m-x)/2]*g[n][x]%P);
cout << Ans << '\n';
return 0;
}
3-C 旅行 (travel.cpp)
树形 dp 形态考虑第一问怎么求,发现子树状态关心「子树根到父亲这条边走的次数」、以及「子树内部的答案」。肯定希望经过根边的次数尽量小,所以尽可能匹配 \(s/t\) 让它们可能不用产生根边的贡献。
现在再考虑一下根边具体有多少条,首先如果子树里面 \(s/t\) 两两匹配,那么贡献是 \(2\times 1\),如果不完全匹配,设子树 \(s/t\) 分别有 \(p/q\) 个,显然至少要向连 \(2\times |p-q|\) 条边。
现在考虑匹配的部分的贡献。手玩一下发现,我们希望在获取某个不匹配(未来匹配)的 \(s\) 前做匹配的部分、或者 \(t\) 后做匹配的部分,消除匹配部分的额外贡献。又发现连接之后未来仍然能将一些东西接在这个段的前后来消除额外贡献,意思是,你可以现在 \(st...st+s\to st...sts\),在未来 \(st...st+st...sts\to st...sts\),或者 \(t+st...st\to tst..st\),在未来 \(tst...st+st...st\to tst...st\),所以你消除额外贡献这个操作对未来是没有后效性的。那么对于根边,贡献就是 \(2\sum \max\{1,|p-q|\}\),这里要去掉空的子树。
现在考虑怎么构造操作方案,先考虑一个点上可以会出现什么形态的东西?完全匹配的段 \(st...st\)、若干绑 \(s\) 的段 \((st...st)s\)、若干绑 \(t\) 的段 \(t(st...st)\),绑 \(s\) 和绑 \(t\) 的段肯定会被匹配掉不共存,完美匹配的段和绑定的段也应该合并消除贡献也不共存,所以一个点上是三种段之一。我们分类讨论一下怎么合并段可以保持子树的一些钦定了的顺序。
- 完全匹配+完全匹配,简单拼接即可。
- 绑 \(s\) + 完全匹配,把完全匹配拼到前缀。
- 绑 \(t\) + 完全匹配,把完全匹配拼到后缀。
- 绑 \(s\) + 绑 \(t\),绑 \(s\) 的放在前面、绑 \(t\) 的放在后面,然后如果条件允许的话把合并出来的完全匹配段按照上面两种方式消除贡献。
快速合并我们可以维护一个链表,实现的时候可以选择更好的顺序,以及可以用启发式合并来合并绑 \(s\) 和绑 \(t\) 的集合来让实现更简便,以及不能使用 queue,空间会爆。
更具体滴,在点上预设链表 \(normal_x\) 表示完全匹配段,\(L_x/R_x\) 表示绑 \(s\) / 绑 \(t\) 的链表集合。
- 递归处理子树 \(y\)。
- 将 \(normal_y\) 接在 \(normal_x\) 上。
- 将 \(L_y/R_y\) 启发式合并到 \(L_x/R_x\) 里。
- 在 \(x\) 上尽量合并 \(L_x/R_x\) 加入到 \(normal_x\)。
- 如果 \(L_x/R_x\) 不都为空,将 \(normal_x\) 合并进去。
#include<bits/stdc++.h>
#define ll long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define hd first
#define tl second
using namespace std;
const int N=600005;
int n, m, tmp, nxt[N], pre[N], tag[N];
ll Ans;
vector<int> to[N];
vector<pii> L[N], R[N];
pii normal[N];
pii lnk(pii i,pii j) {
if(!i.hd) return j;
if(!j.hd) return i;
nxt[i.tl]=j.hd, pre[j.hd]=i.tl;
return mp(i.hd,j.tl);
}
void merge(vector<pii> &a,vector<pii> &b) {
if(b.size()>a.size()) swap(a,b);
for(pii u:b) a.pb(u); b.clear();
}
void dfs(int x,int fad) {
normal[x]=mp(0,0);
if(L[x].size()||R[x].size()) tag[x]=1;
for(int y:to[x]) if(y!=fad) {
dfs(y,x), tag[x]|=tag[y];
normal[x]=lnk(normal[x],normal[y]);
merge(L[x],L[y]);
merge(R[x],R[y]);
}
while(L[x].size()&&R[x].size()) {
pii l=L[x].back(); L[x].pop_back();
pii r=R[x].back(); R[x].pop_back();
normal[x]=lnk(normal[x],lnk(l,r));
}
if(L[x].size()) {
pii u=L[x].back(); L[x].pop_back();
L[x].pb(lnk(normal[x],u)), normal[x]=mp(0,0);
}
if(R[x].size()) {
pii u=R[x].back(); R[x].pop_back();
R[x].pb(lnk(u,normal[x])), normal[x]=mp(0,0);
}
if(tag[x]) Ans+=max(1,(int)L[x].size()+(int)R[x].size());
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
up(i,1,m) cin >> tmp, L[tmp].pb(mp(i,i));
up(i,1,m) cin >> tmp, R[tmp].pb(mp(i+m,i+m));
up(i,2,n) {
int u, v;
cin >> u >> v;
to[u].pb(v);
to[v].pb(u);
}
dfs(1,0), cout << 2*(Ans-1) << '\n';
for(int i=normal[1].hd; i; i=nxt[i]) cout << (i>m?(i-m):i) << ' ';
return 0;
}

浙公网安备 33010602011771号