Codeforces Round #743 (Div. 1)
C. Paint
題目描述
給你一個長度為 \(n\) 的顏色陣列,每次可以選擇一個位置修改它的顏色,此時與他相鄰的極長連續相同顏色段也會改變顏色,問把所有位置變同色的最小操作次數。
\(n\leq 3\cdot 10^3\)
解法
因為每次操作的是一個極長同色連續段,所以可以考慮用區間 \(dp\)
考慮暴力操作需要用 長度\(-1\) 步,但是形如 \(aba\) 操作中間可以少用一步,所以設 \(dp[l][r]\) 表示把區間 \([l,r]\) 染成同色的最大減少操作次數,最後的答案是 \(n-1-dp[1][n]\)
轉移我們需要以減少操作為導向,所以考慮列舉 \(a...a\) 這種情況,我們找到所有 \(s[i]=s[l](l<i\leq r)\)
為什麼是 \(dp[i][r]\) 呢?注意我們的狀態定義中並不涉及最終顏色,但是不難觀察到:可以通過最小操作步數使一個區間變成初始時它邊界上的顏色,所以這兩段就可以合併了。
還有一種簡單的情況是直接繼承,不操作:
\[dp[l][r]\leftarrow dp[l+1][r] \]暴力轉移時間複雜度 \(O(20n^2)\)
總結
\(dp\) 狀態定義注意 \(\min,\max\) 的轉化,這道如果不換成 \(\tt max\) 就不好用到相同顏色數量有限的條件,因為 \(\max\) 是以減少步數為導向的,這是一種正難則反的思想。
//Take me to the top , I am ready for... #include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; const int M = 3005; int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int T,n,a[M],dp[M][M];vector<int> b[M]; void upd(int &x,int y) {x=max(x,y);} void work() { n=read(); for(int i=1;i<=n;i++) { b[i].clear(); for(int j=1;j<=n;j++) dp[i][j]=0; } for(int i=1;i<=n;i++) { a[i]=read(); b[a[i]].push_back(i); } for(int l=n;l>=1;l--) for(int r=l;r<=n;r++) { dp[l][r]=dp[l+1][r]; for(auto x:b[a[l]]) if(l<x && x<=r) upd(dp[l][r],dp[l+1][x-1]+1+dp[x][r]); } printf("%d\n",n-1-dp[1][n]); } signed main() { T=read(); while(T--) work(); }
D. Bridge Club
題目描述
有 \(2^n\) 個人,編號為 \(0\rightarrow 2^n-1\),如果兩個人二進位制位最多相差 \(1\) 就可以配對,每個人最多配對一次,每對的得分為兩個人的點權之和,問最多配 \(k\) 對的最大得分。
\(n\leq 20,k\leq 200\)
解法
可以發現至多 \((2n-1)(k-1)+1\) 條邊有用,桶排之後暴力網路流即可。
總結
縮小問題規模(只考慮和答案有關的量),尋找等價類,是解決不僅限於匹配問題的重要方法。
//I was the king under your control~~
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 20005;
const int N = 1100005;
const int inf = 0x3f3f3f3f;
#define pii pair<int,int>
#define mp make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,t,cnt,a[N],b[N],c[N];vector<pii> w[N<<1];
int S,T,tot,ans,f[M],in[M],dis[M],flow[M],pre[M],lst[M];
struct edge
{
int v,c,f,next;
}e[N];
void add(int u,int v,int F,int c)
{
e[++tot]=edge{v,c,F,f[u]},f[u]=tot;
e[++tot]=edge{u,-c,0,f[v]},f[v]=tot;
}
int bfs()
{
for(int i=0;i<=T;i++) dis[i]=-inf;
queue<int> q;q.push(S);in[S]=1;
dis[S]=flow[T]=0;flow[S]=inf;
while(!q.empty())
{
int u=q.front();q.pop();in[u]=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(e[i].f && dis[v]<dis[u]+c)
{
dis[v]=dis[u]+c;
pre[v]=u;lst[v]=i;
flow[v]=min(flow[u],e[i].f);
if(!in[v]) q.push(v),in[v]=1;
}
}
}
return flow[T]>0;
}
signed main()
{
n=read();k=read();
m=1<<n;t=(2*n-1)*(k-1)+1;tot=1;
for(int i=0;i<m;i++)
{
a[i]=read();
b[i]=__builtin_popcount(i);
}
for(int i=0;i<(1<<n);i++) if(b[i]&1)
for(int j=0;j<n;j++)
{
int to=i^(1<<j);
w[a[i]+a[to]].push_back(mp(i,to));
}
for(int i=2000000;i>=0;i--)
{
for(auto x:w[i])
{
int u=x.first,v=x.second;
if(!c[u]) c[u]=++cnt;
if(!c[v]) c[v]=++cnt;
add(c[u],c[v],1,i);
t--;if(t<0) break;
}
if(t<0) break;
}
S=0;T=++cnt;
for(int i=0;i<m;i++) if(c[i])
{
if(b[i]&1) add(S,c[i],1,0);
else add(c[i],T,1,0);
}
while(bfs())
{
if(dis[T]<=0) break;
int zy=T,tmp=min(flow[T],k);
ans+=dis[T]*tmp;k-=tmp;
if(k==0) break;
while(zy!=S)
{
e[lst[zy]].f-=tmp;
e[lst[zy]^1].f+=tmp;
zy=pre[zy];
}
}
printf("%d\n",ans);
}
F. Stations
題目描述
Caught up in confusion . Need a resolution.
有 \(n\) 個塔臺排成一排,設第 \(i\) 個塔臺的高度是 \(h_i\),覆蓋範圍是 \(w_i\),\(i\) 能覆蓋 \(j\) 的充要條件是:
- \(i\leq j\leq w_i\),\(\forall i<k\leq j,h_k<h_i\)
一開始所有塔臺的高度都為 \(0\),覆蓋範圍都為 \(i\),有下列兩種操作:
- \(op=1\),重建操作,把塔臺 \(x\) 的高度重建成當前最高,覆蓋範圍設定成 \(y\)
- \(op=2\),詢問操作,設 \(b_i\) 表示覆蓋它的塔臺數量,求 \(\sum_{l\leq i\leq r} b_i\)
解法
這道題,是我自己做出來的[驕傲.jpg]
由於每次重建都會獲得最高的塔臺,而且它的有效覆蓋範圍是 \(y\),我們只需要考慮這個塔臺對其他塔臺有效覆蓋範圍的影響即可,不難發現是對 \([1,x)\) 的塔臺對 \(x-1\) 取 \(\min\)
既然是取 \(\min\) 操作我們可以考慮勢能線段樹,所以每個塔臺的有效覆蓋範圍是不難維護的,但是這樣難以處理詢問,我們不妨再拿一棵線段樹維護每個點的被覆蓋次數,再更新有效覆蓋範圍的時候更新它即可。
具體演算法:勢能線段樹在 \(mx>x-1>cx\) 的時候整體取消一個區間的覆蓋即可,時間複雜度 \(O(n\log^2 n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 800005;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,mx[M],cx[M],num[M];ll sum[M],tag[M];
//segment tree II : support simple addition
void Down(int i,int l,int r)
{
if(!tag[i]) return ;
int mid=(l+r)>>1,c=tag[i];
sum[i<<1]+=c*(mid-l+1);tag[i<<1]+=c;
sum[i<<1|1]+=c*(r-mid);tag[i<<1|1]+=c;
tag[i]=0;
}
void Add(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
sum[i]+=(r-l+1)*c;tag[i]+=c;
return ;
}
int mid=(l+r)>>1;Down(i,l,r);
Add(i<<1,l,mid,L,R,c);
Add(i<<1|1,mid+1,r,L,R,c);
sum[i]=sum[i<<1]+sum[i<<1|1];
}
ll Ask(int i,int l,int r,int L,int R)
{
if(l>R || L>r) return 0;
if(L<=l && r<=R) return sum[i];
int mid=(l+r)>>1;Down(i,l,r);
return Ask(i<<1,l,mid,L,R)+
Ask(i<<1|1,mid+1,r,L,R);
}
//segment tree I : support interval Min
void down(int i)
{
mx[i<<1]=min(mx[i<<1],mx[i]);
mx[i<<1|1]=min(mx[i<<1|1],mx[i]);
}
void up(int i)
{
num[i]=0;
mx[i]=max(mx[i<<1],mx[i<<1|1]);
cx[i]=max(cx[i<<1],cx[i<<1|1]);
if(mx[i]==mx[i<<1]) num[i]+=num[i<<1];
if(mx[i]==mx[i<<1|1]) num[i]+=num[i<<1|1];
if(mx[i]!=mx[i<<1]) cx[i]=max(cx[i],mx[i<<1]);
if(mx[i]!=mx[i<<1|1]) cx[i]=max(cx[i],mx[i<<1|1]);
}
void ins(int i,int l,int r,int id,int c)
{
if(l==r)
{
Add(1,1,n,id,mx[i],-1);
mx[i]=c;num[i]=1;
Add(1,1,n,id,mx[i],1);
return ;
}
int mid=(l+r)>>1;down(i);
if(mid>=id) ins(i<<1,l,mid,id,c);
else ins(i<<1|1,mid+1,r,id,c);
up(i);
}
void zxy(int i,int l,int r,int c)
{
if(mx[i]<=c) return ;
if(mx[i]>c && c>cx[i])
{
Add(1,1,n,c+1,mx[i],-num[i]);
mx[i]=c;return ;
}
if(l==r)
{
if(c<mx[i]) Add(1,1,n,c+1,mx[i],-1);
mx[i]=c;return ;
}
int mid=(l+r)>>1;down(i);
zxy(i<<1,l,mid,c);
zxy(i<<1|1,mid+1,r,c);
up(i);
}
void upd(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
zxy(i,l,r,c);
return ;
}
int mid=(l+r)>>1;down(i);
upd(i<<1,l,mid,L,R,c);
upd(i<<1|1,mid+1,r,L,R,c);
up(i);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
ins(1,1,n,i,i);
for(int i=1;i<=m;i++)
{
int op=read(),x=read(),y=read();
if(op==1)
{
ins(1,1,n,x,y);
upd(1,1,n,1,x-1,x-1);
}
else
printf("%lld\n",Ask(1,1,n,x,y));
}
}