test47
跑路
题目看起来很复杂,我们不妨画出一条路径,然后发现是一段取反一段不取反,那么直接 \(f[i][j][0/1]\) 表示走到 \((i,j)\) 状态是不取反/取反最小步数,转移可以先直接转出去,\(f[i][j][0/1]\to f[i+1][j][0/1]/f[i][j+1][0/1]\),然后在点上考虑钦定反不反,\(f[i][j][0]+1\to f[i][j][1],f[i][j][0]=\infty/f[i][j][1]\to f[i][j][0],f[i][j][1]=\infty\)。
#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=1005, inf=1e13;int n, m, a[N][N], f[N][N][2];inline void chk(int &a,int b) { a=min(a,b); }signed main() {freopen("run.in","r",stdin);freopen("run.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n >> m;up(i,1,n) up(j,1,m) cin >> a[i][j];memset(f,0x3f,sizeof(f));if(a[1][1]==0) f[1][1][0]=0; else f[1][1][1]=1;up(i,1,n) up(j,1,m) {if(a[i][j]==0) {chk(f[i][j][0],f[i][j][1]), f[i][j][1]=inf;chk(f[i+1][j][0],f[i][j][0]);chk(f[i][j+1][0],f[i][j][0]);}else {chk(f[i][j][1],f[i][j][0]+1), f[i][j][0]=inf;chk(f[i+1][j][1],f[i][j][1]);chk(f[i][j+1][1],f[i][j][1]);}}cout << min(f[n][m][0],f[n][m][1]) << '\n';return 0;
}
清扫
看看怎么合并,设 \(res_u\) 表示子树 \(u\) 根向上延伸多少个,\(mat_u\) 表示 lca 为 \(u\) 的链有多少个。那么可以发现有关系 \(mat_u=\sum_{v\in son_u} res_v-a_u\),最后更新 \(res_u\),就好了。
#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_backusing namespace std;const int N=100005;int T, n, a[N], flag=1, res[N], mat[N];
vector<int> to[N];void dfs(int x,int fad) {if(to[x].size()==1) return res[x]=a[x], void();for(int y:to[x]) if(y!=fad) dfs(y,x), res[x]+=res[y];mat[x]=res[x]-a[x];if(mat[x]<0) flag=0;res[x]-=2*mat[x];
}void mian() {cin >> n;up(i,1,n) cin >> a[i];if(n==1) {cout << "NO\n";return;}if(n==2) {cin >> n; cin >> n;if(a[1]==a[2]) cout << "YES\n";else cout << "NO\n";return;}up(i,2,n) {int u, v;cin >> u >> v;to[u].pb(v);to[v].pb(u);}up(i,1,n) if(to[i].size()>1) {flag=1, dfs(i,0);if(!flag||res[i]) cout << "NO\n";else cout << "YES\n";break;}up(i,1,n) res[i]=mat[i]=0, to[i].clear();
}signed main() {freopen("clean.in","r",stdin);freopen("clean.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> T;while(T--) mian();return 0;
}
定向
[题解 ARC092F] Two Faced Edges - 洛谷专栏 (luogu.com.cn)
感觉把最短路那里看成 只有什么情况 \(dis=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)
#define pb push_backusing namespace std;const int N=1005, M=200005;int n, m, u[M], v[M], f[N][N], g[N][N];
int stk[N], top, vis[N], gp[N], cnt;
vector<int> F[N], G[N];void dfs1(int x) {for(int y:F[x]) {if(vis[y]) continue;vis[y]=1, dfs1(y);}stk[++top]=x;
}void dfs2(int x) {for(int y:G[x]) {if(gp[y]) continue;gp[y]=cnt, dfs2(y);}
}void dfs(int dis[],int x) {for(int y:F[x]) {if(dis[y]) continue;dis[y]=dis[x]+1;dfs(dis,y);}
}void solve(int dis[],int s) {dis[s]=1, dfs(dis,s);up(i,1,n) --dis[i];
}signed main() {freopen("direct.in","r",stdin);freopen("direct.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n >> m;up(i,1,m) {cin >> u[i] >> v[i];F[u[i]].pb(v[i]);G[v[i]].pb(u[i]);}up(i,1,n) if(!vis[i]) vis[i]=1, dfs1(i);dn(u,n,1) {int i=stk[u];if(gp[i]) continue;gp[i]=++cnt, dfs2(i);}up(i,1,n) solve(f[i],i);up(i,1,n) reverse(F[i].begin(),F[i].end());up(i,1,n) solve(g[i],i);up(i,1,m) {int x=u[i], y=v[i];cout << ((gp[x]==gp[y])^(f[x][y]>1||g[x][y]>1));}return 0;
}
染色
场上就差一点点了,因为 lgjoj 上面是先蓝所以我不保证我下面颜色写对了。
首先直接 dp 真正的形态显得过于复杂了,考虑把颜色段提出来,最后乘上组合数就好了。现在你只要考虑由单个 \(\text{w}\) 隔开的 \(\text{rb}\) 交替段形成的颜色序列有哪些,长度是什么。
现在我们要考虑什么样的颜色段是合法的,又因为 dp 最终序列数你要想一种不会掉可行性的唯一构造方法。
最终会因为 \(\text{w}\) 形成若干段,我们考虑一个连续段怎么操作,第一次操作只能染 \(\text{r}\),第二次操作只能染 \(\text{b}\),之后不管用 \(\text{r/b}\) 都可以做到 \(\text{rb}\) 分别多一个,感觉三次及以后的操作 \(\text{r/b}\) 等价。试试具体的,只有 \(\text{r,r...r,b...b,r...b,b...r}\) 这些情况,发现额外次数其实都是 \(cnt(\text{b})-1\),不知道需不需要补充的,你可以从左到右操作。
现在考虑多个段怎么做呀,显然是,拿到的 \(\text{r}\) 优先给第一次操作,拿到的 \(\text{b}\) 优先拿给第二次操作,然后空余的操作如果可以的话就拿来做额外操作。发现这里让人不任意的点在于,可能会有 \(\text{str=rbrrrb}\) 这种你的额外操作大多只能打在第一个 \(\text{b}\) 为第二次操作的那个段上,所以你的构造方案应该是按照 \(cnt(b)\downarrow\) 去做额外操作的。
填写式的可重集方案数不容易做,但是题目限制颇多,\(cnt(b)\downarrow\) 比划分数小是可以 dfs 出来的,嗯疑似不止这个题目这么处理,那么我们现在可以考虑一下一个 \(cnt(b)\downarrow\) 的序列的贡献。首先有一个可重集排列的方案数乘进去,然后发现剩下的问题是把 \(n\) 填进若干非空段和若干可空段。
强制非空的:\(m-1\) 个整段间的 \(\text{w}\),\(cnt(b)=0\) 的一个 \(\text{r}\),\(cnt(b)>1\) 的最左右 \(\text{b}\) 夹着的 \(2cnt(b)-1\) 个段。
可以空的:整段最左/右的 \(\text{w}\),\(cnt(b)>1\) 的最左右边的 \(\text{r}\)。
考虑前者 \(l\) 个,后者 \(r\) 个,那么等价于 \(n+r\) 个放进 \(l+r\) 个强制非空的桶,方案数 \(\binom{n+r-1}{l+r-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)
#define pb push_backusing namespace std;const int N=1005, P=1e9+7;int n, k, ans, a[N], mul[N], inv[N], riv[N];
char str[N];int C(int n,int m) {if(m<0||n<m) return 0;return mul[n]*riv[m]%P*riv[n-m]%P;
}inline void add(int &a,int b) { a=(a+b)%P; }void solve(int m) {if(!m) return ++ans, void();int i=0, j=0, af=0;up(u,1,k) {if(str[u]=='r') {if(i<m) ++i;else if(af>0) --af;}else {if(j<i&&a[j+1]) af+=a[++j]-1;else if(af>0) --af;}}if(i<m||j<m&&a[j+1]||af) return;int cnt=m-1, res=2;up(i,1,m) {if(!a[i]) ++cnt;else res+=2, cnt+=2*a[i]-1;}int val=C(n+res-1,cnt+res-1);for(int l=1, r=1; l<=m; l=r+1, r=l) {while(r<m&&a[r+1]==a[l]) ++r;val=val*C(m-l+1,r-l+1)%P;}(ans+=val)%=P;
}void dfs(int m,int sum) {if(m-1+sum>n) return;solve(m);dn(v,((m==0)?n:a[m]),0) a[m+1]=v, dfs(m+1,sum+(v?(2*v-1):1));
}signed main() {freopen("color.in","r",stdin);freopen("color.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);mul[0]=inv[0]=inv[1]=riv[0]=1;up(i,1,1000) mul[i]=mul[i-1]*i%P;up(i,2,1000) inv[i]=inv[P%i]*(P-P/i)%P;up(i,1,1000) riv[i]=riv[i-1]*inv[i]%P;cin >> n >> k >> (str+1);dfs(0,0);cout << (ans%P+P)%P << '\n';return 0;
}