感动渐渐化为平静的水面 虚假美好回忆变成不可逆爱恋
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_back
using 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_back
using 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_back
using 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;
}

浙公网安备 33010602011771号