做题纪要
Alice and Recoloring 1
有一个很牛逼的转化,考虑一个点 \(i,j\) 是否被以此为端点进行区间覆盖,只需考虑 \((i+1,j)\),\((i,j+1)\),\((i+1,j+1)\) 是为 \(B\) 的个数,如果个数为偶数,则此点不许操作,否则则需操作。设原序列 \(a_{i,j}=[s_{i,j}='B']\) , \(c_{i,j}=a_{i+1,j} \oplus a_{i,j+1} \oplus a_{i+1,j+1} \oplus a_{i,j}\),所以将 \(a\) 清空其实就是将 \(c\) 清空,而且还有一个比较好的地方就是进行操作一其实就是修改一个点,而进行操作四就是修改四个点形如:\((x+1,y+1)\) \(\rightarrow\) \((x,y)\),\((x,m)\),\((n,y)\),\((n,m)\),再考虑,操作 \(2,3\)就是在搞笑,完全可以被两次操作一代替,还有两次操作四可以被六次操作一代替,所以操作四最多进行一次,然后直接枚举那一次选在哪里就可以。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505;
char s[N];
int c[N][N];
int a[N][N];
int sum[N][N];
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++){
if(s[j]=='B') c[i][j]=1;
else c[i][j]=0;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=(c[i][j]^c[i+1][j]^c[i][j+1]^c[i+1][j+1]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
sum[i][j]+=a[i][j];
}
}
int ans=sum[n][m];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int w;
w=a[n][m]+a[i-1][j-1]+a[n][j-1]+a[i-1][m];
ans=min(ans,3+sum[n][m]-w+4-w);
}
}
printf("%lld",ans);
}
Alice and Recoloring 2
转换和上道题一样,唯一不同的就是两次操作四比六次会比操作一优,所以操作四可以操作很多次,但还有一些其他性质需要挖掘:
-
若两次四操作为 \((x,y1)\) 和 \((x,y2)\) 则会改变四个 \(c\) 值,则可以用四次操作一代替,纵坐标相等横坐标不同同理,所以四操作不会出现在同一行同一列。
-
四操作假如 \((x,m)\),\((x,y)\),\((n,y)\) 不全为一,则会产生一次负贡献,需要用一代价的操作一代替(不可能是操作四,由👆可知),所以仅反转三个格子总代价为 \(2+1=3\),可以用一代替,所以只有那三个格子均为一,才会进行操作四。
所以肯定是合法的操作四越多越好,为了求这个,也就是二分图最大匹配,考虑 Dinic ,左侧 \(n-1\) 个点,右侧 \(m-1\) 个点,对于合法四操作连一条边权为 \(1\) 的边,跑最大流就可以力。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=600;
const int M=360000;
const int inf=0x3f3f3f3f3f;
char s[N];
int c[N][N];
int a[N][N];
int st,ed;
int head[M*2],ver[M*2],nex[M*2],edge[M*2],tot=1;
void add(int x,int y,int v){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=v;
ver[++tot]=x,nex[tot]=head[y],head[y]=tot,edge[tot]=0;
}
int dep[M],cur[M];
int bfs(){
memset(dep,0,sizeof(dep));
dep[st]=1;
queue<int> q;
q.push(st);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(dep[y] || edge[i]==0) continue;
dep[y]=dep[x]+1;
q.push(y);
}
}
if(dep[ed]) return 1;
else return 0;
}
int dfs(int x,int flow){
if(x==ed) return flow;
for(int i=cur[x];i;i=nex[i]){
cur[x]=i;
int y=ver[i];
if(dep[y]==dep[x]+1 && edge[i]){
int d=dfs(y,min(flow,edge[i]));
if(d>0){
edge[i]-=d;
edge[i^1]+=d;
return d;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=ed;i++)
cur[i]=head[i];
while(int d=dfs(st,inf)){
ans+=d;
}
}
return ans;
}
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
int sum=0;
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++){
if(s[j]=='B') c[i][j]=1;
else c[i][j]=0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=(c[i][j]^c[i+1][j]^c[i][j+1]^c[i+1][j+1]);
if(a[i][j]) sum++;
}
}
st=n+m+1,ed=st+1;
for(int i=1;i<n;i++) add(st,i,1);
for(int i=1;i<m;i++) add(n+i,ed,1);
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(a[i][j] && a[i][m] && a[n][j]){
add(i,n+j,1);
}
}
}
int cnt=Dinic();
int ans=0;
ans+=cnt*2;
int w=sum-cnt*3;
if(a[n][m] && cnt%2) ans--;
if(!a[n][m] && cnt%2) ans++;
printf("%lld",ans);
return 0;
}
[ABC324F] Beautiful Path
题干要求 \(\frac{\sum b}{\sum c}\) 最大,所以可以二分一个最大值 \(k\),然后就变成了 \(\frac{\sum b}{\sum c} \geq k\),移过去变为 \(\sum b-\sum c \times k \geq 0\),然后边权就变为了 \(b-k \times c\),然后拓扑跑最大路,看是否大于等于0。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2*1e5+10;
const long double eps=1e-15;
int head[N*2],ver[N*2],nex[N*2],tot=0;
long double edge[N*2];
void add(int x,int y,long double v){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=v;
}
struct asd{
int x,y;
long double b,c;
}a[N];
int n,m;
long double dp[N];
bool dij(int p){
for(int i=1;i<=n;i++) dp[i]=-(1<<25);
dp[p]=0;
for(int x=1;x<n;x++){
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
dp[y]=max(dp[y],dp[x]+edge[i]);
}
}
return dp[n]>=0;
}
int check(long double k){
tot=0;
memset(head,0,sizeof(head));
for(int i=1;i<=m;i++){
int x=a[i].x,y=a[i].y;
long double w=a[i].b-k*a[i].c;
add(x,y,w);
}
return dij(1);
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++) scanf("%lld%lld%Lf%Lf",&a[i].x,&a[i].y,&a[i].b,&a[i].c);
long double l=0,r=21e9;
while(r-l>eps){
long double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%0.20Lf",l);
}
「KDOI-06-S」树上异或
树形 \(dp\) 确实很显然,按位计算也很显然,然后就不会了,\(lhx\) 是强的,这也很显然,可以把这个分成两部分相乘,一部分是完整的块的乘积,另一部分是还未乘上贡献的方案数,转移的时候一个一个合并到一起,所以设 \(f_{i,j,0/1}\) 表示以 \(i\) 为根的子树形成的块在二进制下第 \(j\) 位的且不完整块有偶数/奇数个 \(j\) 的贡献,所以
然后考虑断边的操作 ,其实就是将当前不完整块个数为奇数的贡献计算出来,然后将其加到全部的 \(f_{x,j,0}\) 上即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5*1e5+5;
const int mod=998244353;
int f[N][65][3];
long long a[N];
int head[N*2],ver[N*2],nex[N*2],tot=0;
void add(int x,int y){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int dfs(int x,int fa){
for(int i=1;i<=61;i++){
if(a[x]&(1ll<<(i-1))) f[x][i][1]=1;
else f[x][i][0]=1;
}
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa) continue;
dfs(y,x);
for(int j=1;j<=61;j++){
int f0=f[x][j][0],f1=f[x][j][1];
f[x][j][0]=1ll*(1ll*f0*f[y][j][0]%mod+1ll*f1*f[y][j][1]%mod)%mod;
f[x][j][1]=1ll*(1ll*f0*f[y][j][1]%mod+1ll*f1*f[y][j][0]%mod)%mod;
}
}
long long sum=0;
for(int i=1;i<=61;i++) sum=1ll*(1ll*sum+1ll*(1ll<<(i-1))%mod*f[x][i][1]%mod)%mod;
for(int i=1;i<=61;i++) f[x][i][0]=1ll*(1ll*f[x][i][0]+1ll*sum)%mod;
return sum;
}
signed main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=2;i<=n;i++){
int x;
scanf("%d",&x);
add(i,x),add(x,i);
}
long long ans=dfs(1,0);
printf("%lld",ans);
}

浙公网安备 33010602011771号