1. 消耗戰(P2495)
題目描述
在一場戰爭中,戰場由 \(n\) 個島嶼和 \(n-1\) 個橋樑組成,保證每兩個島嶼間有且僅有一條路徑可達。現在,我軍已經偵查到敵軍的總部在編號為 \(1\) 的島嶼,而且他們已經沒有足夠多的能源維繫戰鬥,我軍勝利在望。已知在其他 \(k\) 個島嶼上有豐富能源,為了防止敵軍獲取能源,我軍的任務是炸燬一些橋樑,使得敵軍不能到達任何能源豐富的島嶼。由於不同橋樑的材質和結構不同,所以炸燬不同的橋樑有不同的代價,我軍希望在滿足目標的同時使得總代價最小。
偵查部門還發現,敵軍有一臺神秘機器。即使我軍切斷所有能源之後,他們也可以用那臺機器。機器產生的效果不僅僅會修復所有我軍炸燬的橋樑,而且會重新隨機資源分佈(但可以保證的是,資源不會分佈到 \(1\) 號島嶼上)。不過偵查部門還發現了這臺機器只能夠使用 \(m\) 次,所以我們只需要把每次任務完成即可。
輸入格式
第一行一個整數 \(n\),表示島嶼數量。
接下來 \(n-1\) 行,每行三個整數 \(u,v,w\) ,表示 \(u\) 號島嶼和 \(v\) 號島嶼由一條代價為 \(w\) 的橋樑直接相連。
第 \(n+1\) 行,一個整數 \(m\) ,代表敵方機器能使用的次數。
接下來 \(m\) 行,第 \(i\) 行一個整數 \(k_i\) ,代表第 \(i\) 次後,有 \(k_i\) 個島嶼資源豐富。接下來 \(k_i\) 個整數 \(h_1,h_2,..., h_{k_i}\) ,表示資源豐富島嶼的編號。
輸出格式
輸出共 \(m\) 行,表示每次任務的最小代價。
資料規模與約定
- 對於 \(10\%\) 的資料,\(n\leq 10, m\leq 5\) 。
- 對於 \(20\%\) 的資料,\(n\leq 100, m\leq 100, 1\leq k_i\leq 10\) 。
- 對於 \(40\%\) 的資料,\(n\leq 1000, 1\leq k_i\leq 15\) 。
- 對於 \(100\%\) 的資料,\(2\leq n \leq 2.5\times 10^5, 1\leq m\leq 5\times 10^5, \sum k_i \leq 5\times 10^5, 1\leq k_i< n, h_i\neq 1, 1\leq u,v\leq n, 1\leq w\leq 10^5\) 。
簡化一下就是每次詢問給一堆關鍵點,每次可以刪一些邊,刪邊有代價,求所有關鍵點與根不連通的最小代價。
暴力 DP 顯然,但 \(O(qn)\) 過不了一點。
考慮典中典虛樹,虛樹建完之後咋搞呢,發現如果把一個關鍵點和根隔開的最小代價就是直接刪到根路徑中最小的邊,dfs 預處理一下就和暴力 dp 沒區別了。
要記住虛樹咋求的,單調棧維護一條鏈,如果賽時寫掛了就根號分治騙分。
#include<bits/stdc++.h>
namespace Limie{
#define x first
#define y second
#define int long long
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
}using namespace Limie;
int n,m;
int h[500010],e[1000010],w[1000010],ne[1000010],idx;
int a[500010],dfn[500010],cnt;
int f[500010][20],d[500010],mn[500010];
int stk[500010],top,k;
bool st[500010];
void add(int a,int b,int c){e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;}
void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
void dfs(int u,int fa)
{
dfn[u]=++cnt;
f[u][0]=fa;
d[u]=d[fa]+1;
for(int i=1;i<20;i++)f[u][i]=f[f[u][i-1]][i-1];
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(v!=fa){
if(u!=1)mn[v]=min(mn[u],w[i]);
else mn[v]=w[i];
dfs(v,u);
}
}
}
int lca(int x,int y)
{
if(d[x]<d[y])x^=y^=x^=y;
for(int i=19;i>=0;i--)
if((1<<i)<=d[x]-d[y])x=f[x][i];
if(x==y)return x;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
void build()
{
sort(a+1,a+k+1,cmp);
stk[top=1]=1,h[1]=-1;
for(int i=1;i<=k;i++){
if(a[i]!=1){
int l=lca(a[i],stk[top]);
if(l!=stk[top]){
while(dfn[l]<dfn[stk[top-1]])add(stk[top-1],stk[top]),top--;
if(dfn[l]!=dfn[stk[top-1]]) {
h[l]=-1;
add(l,stk[top]),stk[top]=l;
}else add(l,stk[top--]);
}
h[a[i]]=-1;
stk[++top]=a[i];
}
}
for(int i=1;i<top;i++)add(stk[i],stk[i+1]);
}
int dp(int u)
{
int ans=0;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
ans+=dp(v);
}if(u==1)return ans;
if(st[u])return mn[u];
else return min(ans,mn[u]);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int i;
cin>>n;
memset(h,-1,sizeof h);
for(i=1;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c),add(b,a,c);
}
dfs(1,1);
memset(h,-1,sizeof h);
cin>>m;
while(m--){
cin>>k;
idx=0;
for(i=1;i<=k;i++){
cin>>a[i];
st[a[i]]=1;
}
build();
cout<<dp(1)<<'\n';
for(i=1;i<=k;i++)st[a[i]]=0;
}
}
2.最大 m 子段和(加強版)/CF280D (加強加強版)
先考慮 CF280D。
題面
長度為 \(n\) 的數列,支援兩種操作:
1.修改某個位置的值
2.詢問區間 \([l,r]\) 裡選出至多 \(k\) 個不相交的子段和的最大值。
一共有 \(m\) 個操作。
如果 \(k=1\) 隨便做,線段樹秒了喵。
發現不會 \(k>1\) 喵。
思考貪心演算法,發現可以反悔,就是每次貪心取最大子段和喵,取完後把區間取反,如果取到了兩個區間有交,那麼重合部分相加為 \(0\) 喵,所以這個貪心是真的喵。
隨便寫寫就過了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m;
#define mid (l+r>>1)
struct Extm{
int l,r,ans;
Extm operator+(const Extm &w)const{return {l,w.r,w.ans+ans};}
bool operator<(const Extm &y)const{return ans<y.ans;}
bool operator>(const Extm &y)const{return ans>y.ans;}
};
struct Node{
int l,r;
Extm sum;
Extm premax,sufmax,ansmax;
Extm premin,sufmin,ansmin;
bool tag;
}tr[400010];
Node operator+(const Node &x,const Node &y)
{
Node u;
u.sum=x.sum+y.sum;
u.premax=max(x.premax,x.sum+y.premax);
u.sufmax=max(y.sufmax,x.sufmax+y.sum);
u.ansmax=max({x.ansmax,y.ansmax,x.sufmax+y.premax});
u.premin=min(x.premin,x.sum+y.premin);
u.sufmin=min(y.sufmin,x.sufmin+y.sum);
u.ansmin=min({x.ansmin,y.ansmin,x.sufmin+y.premin});
u.tag=0;
return u;
}
void pushtag(int u)
{
swap(tr[u].premin,tr[u].premax);
swap(tr[u].sufmin,tr[u].sufmax);
swap(tr[u].ansmin,tr[u].ansmax);
tr[u].premin.ans*=-1,tr[u].premax.ans*=-1;
tr[u].sufmin.ans*=-1,tr[u].sufmax.ans*=-1;
tr[u].ansmin.ans*=-1,tr[u].ansmax.ans*=-1;
tr[u].sum.ans*=-1;
tr[u].tag^=1;
}
void pushdown(int u)
{
if(!tr[u].tag)return;
pushtag(u<<1),pushtag(u<<1|1);
tr[u].tag=0;
}
void newnode(int u,int x,int c)
{
tr[u].premin=tr[u].premax={x,x,c};
tr[u].sufmin=tr[u].sufmax={x,x,c};
tr[u].ansmin=tr[u].ansmax={x,x,c};
tr[u].sum={x,x,c},tr[u].tag=0;
}
void modify(int u,int l,int r,int x,int c)
{
if(l==r)return newnode(u,x,c);
pushdown(u);
if(x<=mid)modify(u<<1,l,mid,x,c);else modify(u<<1|1,mid+1,r,x,c);
tr[u]=tr[u<<1]+tr[u<<1|1];
}
void reverse(int u,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return pushtag(u);
pushdown(u);
if(L<=mid)reverse(u<<1,l,mid,L,R);
if(mid<R)reverse(u<<1|1,mid+1,r,L,R);
tr[u]=tr[u<<1]+tr[u<<1|1];
}
Node query(int u,int l,int r,int L,int R)
{
if(L<=l&&r<=R)return tr[u];
pushdown(u);
if(R<=mid)return query(u<<1,l,mid,L,R);
if(mid<L)return query(u<<1|1,mid+1,r,L,R);
return query(u<<1,l,mid,L,R)+query(u<<1|1,mid+1,r,L,R);
}
#undef mid
queue<Node> q;
int main()
{
int i;
cin>>n;
for(i=1;i<=n;i++){
int x;
cin>>x;
modify(1,1,n,i,x);
}
cin>>m;
while(m--){
int ty,l,r,k;
cin>>ty>>l>>r;
if(ty){
cin>>k;
int s=0;
while(k--){
Node ans=query(1,1,n,l,r);
if(ans.ansmax.ans<=0)break;
q.push(ans),s+=ans.ansmax.ans;
reverse(1,1,n,ans.ansmax.l,ans.ansmax.r);
}
cout<<s<<'\n';
while(q.size()){
Node ans=q.front();
reverse(1,1,n,ans.ansmax.l,ans.ansmax.r);
q.pop();
}
}else modify(1,1,n,l,r);
}
}
弱化版很簡單喵,題意就是沒有修改且區間為整個序列。
有神秘簡單做法,挖個坑,回頭補喵。
貼一份來自 wmh 的神仙程式碼。
#include<bits/stdc++.h>
using namespace std;
int a[1000005],zt[1000005];
long long b[1000005];
int nxt[1000005],pre[1000005],vist[1000005];
priority_queue<pair<long long,int>>pq;
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&a[i]),zt[i]=(a[i]>=0);
long long ans=0;
int t=0;
for(int i=1;i<=n;){
int j=i;long long h=0;
while(j<=n&&zt[j]==zt[i])h+=a[j++];
if(zt[i])ans+=h;
if(zt[i]||(i>1&&j<=n))b[++t]=(h<0?h:-h);
i=j;
}
int k=max(0,(t+1)/2-m);
for(int i=1;i<=t;i++)pq.push(make_pair(b[i],i));
b[0]=b[t+1]=-1e18;
for(int i=1;i<t;i++)nxt[i]=i+1;
for(int i=2;i<=t;i++)pre[i]=i-1;
while(k--){
int aa=pq.top().second;
if(vist[aa]){
pq.pop();
k++;
continue;
}
ans+=pq.top().first;
pq.pop();
int a1=pre[aa],a2=nxt[aa];
nxt[pre[a1]]=pre[nxt[a2]]=aa;
pre[aa]=pre[a1],nxt[aa]=nxt[a2];
vist[a1]=vist[a2]=1;
pq.push(make_pair(b[aa]=b[a1]+b[a2]-b[aa],aa));
}
printf("%lld\n",ans);
return 0;
}
3. 和諧矩陣(P3164)
題目描述
我們稱一個由 \(0\) 和 \(1\) 組成的矩陣是和諧的,當且僅當每個元素都有偶數個相鄰的 \(1\)。一個元素相鄰的元素包括它本身,及他上下左右的 \(4\) 個元素(如果存在)。給定矩陣的行數和列數,請計算並輸出一個和諧的矩陣。注意:所有元素為 \(0\) 的矩陣是不允許的。
輸入格式
輸入一行,包含兩個空格分隔的整數 \(m\) 和 \(n\),分別表示矩陣的行數和列數。
輸出格式
輸出包含 \(m\) 行,每行 \(n\) 個空格分隔整數(\(0\) 或 \(1\)),為所求矩陣。測試資料保證有解。
一眼高消,跟昨天做的外星千足蟲沒有區別喵。
#include<bits/stdc++.h>
namespace Limie{
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
}using namespace Limie;
int n,m;
int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
bitset<1610> a[1610];
void gauss()
{
for(int i=0;i<n*m;i++){
for(int j=i+1;j<n*m;j++)
if(a[j][i]){swap(a[i],a[j]);break;}
if(!a[i][i])a[i][i]=a[i][n*m]=1;
for(int j=0;j<n*m;j++)
if(j!=i&&a[j][i])a[j]^=a[i];
}
for(int i=0;i<n*m;i++){
if(i&&i%n==0)cout<<'\n';
cout<<a[i][n*m]<<' ';
}
}
int main()
{
int i,j;
cin>>n>>m;
for(i=0;i<n;i++)
for(j=0;j<m;j++){
a[i*m+j][i*m+j]=1;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x<0||x>=n||y<0||y>=m)continue;
a[i*m+j][x*m+y]=1;
}
}
gauss();
}
4. ABC356
今晚有點原神。
D 使用 1<<x
結果 x=-1
,所以卡了一會喵。
F 是 fhq-treap 板子,調調細節就可以了喵。
G 不會喵。