2022ICPC区域赛(南京站)部分题解


I. Perfect Palindrome

签到

G. Inscryption

签到,反悔贪心

D. Chat Program

题意:

给定一个序列a,和整数k,m,c,d,可以进行最多一次操作,将长度为m的连续部分的每个数,与首项为c,公差为d的等比数列相加,最大化序列的第k大值

思路:

  • 维护长度为m的滑动窗口,每次移动一个单位,滑动窗口的首项会从c+id变成c+id+d,相当于整体序列+d,维护一个偏移量,对每个滑动窗口二分答案即可,计算>=mid的数有多少个,可以使用权值树状数组来查找

时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define int long long
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> PII;
const int N=1e5+10;
struct BIT{
    vector<int> c;
    int n;
    void init(int nn){
        n=nn;
        c=vector<int> (nn);
    }
    void add(int x,int y){
        for(int i=x;i<=n;i+=i&-i)c[i]+=y;
    }
    int query(int x){
        int ans=0;
        for(int i=x;i;i&=i-1)ans+=c[i];
        return ans;
    }
};
void solve(){
    int n,k,m,c,d;
    cin>>n>>k>>m>>c>>d;
    vector<int> a(n+1);
    for(int i=1;i<=n;i++)cin>>a[i];
    vector<int> nums;
    for(int i=1;i<=n;i++)nums.push_back(a[i]),nums.push_back(a[i]+c+(i-1)*d);
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    auto id=[&](int x){
        int t=lower_bound(nums.begin(),nums.end(),x)-nums.begin()+1;
        return nums.size()-t+1;
    };
    BIT t1,t2;
    t1.init(nums.size()+10);
    t2.init(nums.size()+10);
    for(int i=1;i<=n;i++)t2.add(id(a[i]),1);
    int offset=0,ans=0;
    auto check=[&](int mid){
        int x=id(mid);
        int res=t1.query(x);
        mid-=offset;
        {
            x=id(mid);
            res+=t2.query(x);
        }
        return res>=k;
    };
    for(int i=1,j=1;i<=n;i++){
        t1.add(id(a[i]+c+(i-1)*d),1);
        t2.add(id(a[i]),-1);
        if(i-j+1>m){
            t1.add(id(a[j]+c+(j-1)*d),-1);
            t2.add(id(a[j]),1);
            j++;
        }
        if(i-j+1==m){
            int l=0,r=1e18;
            while(l<r){
                int mid=l+r+1>>1;
                if(check(mid))l=mid;
                else r=mid-1;
            }
            ans=max(ans,l-offset);
            // cout<<ans<<" "<<i<<" "<<l<<" "<<offset<<"\n";
            offset+=d;
        }
    }   
    cout<<ans<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    // cin>>T;
    while(T--)solve();
    return 0;
}

A. Stop, Yesterday Please No More

题意:

给定一个n*m的网格,每个网格有个袋鼠,还有一个包含"U",“D”,“L”,"R"的操作序列,网格内还存在一个洞,执行完操作序列后,网格上还有k只袋鼠,询问洞的位置有多少个?

思路:

  • 首先假设没有洞,那么最终剩余在网格上的袋鼠是一个矩形,这个矩形在移动时必然不会离开边界

  • 因此求出这个矩形的左上顶点后,模拟这个矩形移动,每次对矩形内的所有点+1,最后每个网格的值即当洞在此时能掉进去的袋鼠数量

  • 可以使用二维差分做,最后求和判断即可

时间复杂度: O ( ∣ S ∣ + n m ) O(|S|+nm) O(S+nm)

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1e3+10;
void solve(){
    int n,m,k;
    cin>>n>>m>>k;
    vector<vector<int>> b(n+10,vector<int>(m+10));
    string s;
    cin>>s;
    int L,R,U,D,l,r,u,d;
    L=U=l=u=1,R=r=m,D=d=n;
    for(auto ch:s){
        if(ch=='L')l++,r++;
        if(ch=='R')l--,r--;
        if(ch=='D')u--,d--;
        if(ch=='U')u++,d++;
        L=max(L,l);
        R=min(R,r);
        D=min(D,d);
        U=max(U,u);
    }
    if(L>R||U>D){
        if(k==0)cout<<n*m<<"\n";
        else cout<<"0\n";
        return;
    }
    int area=max(R-L+1,0ll)*max(D-U+1,0ll);
    area-=k;
    if(area<0){
        cout<<"0\n";
        return;
    }
    int x=U,y=L;
    int lenx=D-U+1,leny=R-L+1;
    auto add=[&](){
        int xx=x+lenx-1,yy=y+leny-1;
        b[xx+1][yy+1]++;
        b[x][y]++;
        b[xx+1][y]--;
        b[x][yy+1]--;
    };
    vector<vector<bool>> vis(n+1,vector<bool> (m+1));
    vis[x][y]=1;
    add();
    for(auto ch:s){
        if(ch=='L')y--;
        else if(ch=='R')y++;
        else if(ch=='U')x--;
        else x++;
        if(vis[x][y])continue;
        vis[x][y]=1;
        add();
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            b[i][j]+=b[i][j-1]+b[i-1][j]-b[i-1][j-1];
            if(b[i][j]==area)ans++;
        }
    }
    cout<<ans<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}
/*
3 
4 5 3 
ULDDRR 
4 5 0 
UUUUUUU 
4 5 10 
UUUUUUU
*/

M. Drain the Water Tank

题意:

给定n个顶点形成的多边形水箱,问需要在多边形开多少个口才能把水全部流出
1 ≤ n ≤ 2 ⋅ 1 0 3 1 \leq n \leq 2 \cdot 10^3 1n2103

思路:

  • 顶点逆时针给出,顶点x从左到右上方在多边形内部,从右到左上方在多边形外部
  • 可以发现需要开点的形状要么是个凹下去的尖角,要么是凹下去的平面,判断即可

时间复杂度:
O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int cross(int x1,int y1,int x2,int y2){
    return x1*y2-x2*y1;
}
void solve(){
    int n;
    cin>>n;
    vector<int> X(n),Y(n);
    for(int i=0;i<n;i++)cin>>X[i]>>Y[i];
    int ans=0;
    for(int i=0,j=1;i<n;i++){
        while(Y[i]==Y[j])j=(j+1)%n;
        int pre=(i+n-1)%n;
        if(Y[i]<Y[pre]&&Y[i]<Y[j]){
            if(Y[i]!=Y[(i+1)%n]){
                if(cross(X[i]-X[pre],Y[i]-Y[pre],X[j]-X[i],Y[j]-Y[i])>0)ans++;
            }
            else{ 
                if(X[(i+1)%n]>X[i])ans++;
            }
        }
    }
    cout<<ans<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    // cin>>T;
    while(T--)solve();
    return 0;
}

B. Ropeway

题意:

在距离索道入口0和(n+1)单位距离的位置有索道站,给
出在1,2,··· ,n 单位距离架设支撑塔的成本,分别是
a1, a2,··· ,an。要求相邻支撑塔或索道站之间的距离必须小
于等于k。

成本序列会进行q次临时的修改(之后会复原),求出架设
支撑塔的最小总成本。

1 ≤ n ≤ 5 ⋅ 1 0 5 , 1 ≤ k ≤ m i n ( n + 1 , 3 ⋅ 1 0 3 ) , 1 ≤ q ≤ 3 ⋅ 1 0 3 1\leq n \leq 5 \cdot 10^5 ,1 \leq k \leq min(n+1,3 \cdot 10^3),1 \leq q \leq 3 \cdot 10^3 1n5105,1kmin(n+1,3103),1q3103

思路:

  • 假设没有修改,dp方程显然 f i 表示从 0 到 i 的最小代价, f i = m i n f j + a i ( i − j ≤ k ) f_i表示从0到i的最小代价,f_i=min f_j+a_i(i-j \leq k) fi表示从0i的最小代价,fi=minfj+ai(ijk)
  • 可以使用单调队列优化成线性
  • 此时加入修改,假设修改i,i后面的f都要变化
  • 定义 g i 表示 i + 1 到 n + 1 的最小代价 , 则答案为 f i + g i 定义g_i表示i+1到n+1的最小代价,则答案为f_i+g_i 定义gi表示i+1n+1的最小代价,则答案为fi+gi
  • 此时改变 f i ,只需要重新计算从 i 开始后 k 位的 f 来重新贡献答案 此时改变f_i,只需要重新计算从i开始后k位的f来重新贡献答案 此时改变fi,只需要重新计算从i开始后k位的f来重新贡献答案
  • q,k都很小,可以暴力更新一遍,最后复原即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
void solve(){
    int n,k;
    cin>>n>>k;
    vector<int> a(n+10),f(n+10),g(n+10),q(n+10);
    for(int i=1;i<=n;i++)cin>>a[i];
    string s;
    cin>>s;
    s=" "+s;
    int hh=0,tt=-1;
    for(int i=0;i<=n+1;i++){
        while(hh<=tt&&i-q[hh]>k)hh++;
        f[i]=f[q[hh]]+a[i];
        if(s[i]=='1')hh=0,tt=-1;
        while(hh<=tt&&f[q[tt]]>f[i])tt--;
        q[++tt]=i;
    }
    hh=0,tt=-1;
    for(int i=n+1;i>=0;i--){
        while(hh<=tt&&q[hh]-i>k)hh++;
        g[i]=g[q[hh]]+a[i];
        if(s[i]=='1')hh=0,tt=-1;
        while(hh<=tt&&g[q[tt]]>g[i])tt--;
        q[++tt]=i;
    }
    for(int i=0;i<=n+1;i++)g[i]-=a[i];
    int Q;
    cin>>Q;
    vector<int> F=f;
    while(Q--){
        int p,v;
        cin>>p>>v;
        int tmp=a[p];
        a[p]=v;
        hh=0,tt=-1;
        for(int i=max(0ll,p-k);i<p;i++){
            while(hh<=tt&&i-q[hh]>k)hh++;
            if(s[i]=='1')hh=0,tt=-1;
            while(hh<=tt&&f[q[tt]]>f[i])tt--;
            q[++tt]=i;
        }
        for(int i=p;i<=min(p+k-1,n+1);i++)F[i]=f[i];
        int res=1e18;
        for(int i=p;i<=min(p+k-1,n+1);i++){
            while(hh<=tt&&i-q[hh]>k)hh++;
            f[i]=f[q[hh]]+a[i];
            res=min(res,f[i]+g[i]);
            if(s[i]=='1')hh=0,tt=-1;
            while(hh<=tt&&f[q[tt]]>f[i])tt--;
            q[++tt]=i;
        }
        cout<<res<<"\n";
        for(int i=p;i<=min(p+k-1,n+1);i++)f[i]=F[i];
        a[p]=tmp;
    }
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    cin>>T;
    while(T--)solve();
    return 0;
}

J. Perfect Matching

题意:
给定一张包含n个顶点的无向图(n是偶数)以及n个整数
a1, a2,··· ,an。对于任意满足 1 ≤ i <j ≤n 的正整数 i 和 j,
若|i−j| = |ai −aj|(|x| 表示 x 的绝对值)则在无向图中顶
点i 和顶点j之间连一条无向边。

求一个完美匹配,或表明不存在完美匹配。

完美匹配即用n/2条边将n个顶点匹配,任意点都有一条边相连

思路:

  • i + a i = j + a j 或者 i − a i = j − a j 就有一条边 i+a_i=j+a_j或者i-a_i=j-a_j就有一条边 i+ai=j+aj或者iai=jaj就有一条边
  • 对于每个 i , i + a i 与 i − a i 连边,如果 i , j 匹配那么他们对应的边有一个公共点 对于每个i,i+a_i与i-a_i连边,如果i,j匹配那么他们对应的边有一个公共点 对于每个ii+aiiai连边,如果ij匹配那么他们对应的边有一个公共点
  • 问题转化成将一张图分解成若干条边不相交(点可以相交)的长度为2的链。
  • 这是一个经典问题,参考POJ3222和CF858F
  • 首先每个连通分量边必须为偶数
  • 先随便找一棵 dfs 树,然后从深到浅考虑每一个点。找到所有和它相连的未被匹配的边,除了它连向父亲的边(这条边显然未被匹配)。如果这些边是偶数条,两两匹配即可,连向父亲的边会在处理父亲时被匹配上。如果这些边是奇数条,就把连向父亲的边也加入匹配。
  • 时间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int a[N],deg[N],dep[N];
bool vis[N];
vector<PII> adj[N],ans;
int bfs(int S){
    queue<int> q;
    q.push(S);
    int res=0;
    vis[S]=1;
    while(!q.empty()){
        auto u=q.front();
        q.pop();
        res+=deg[u];
        for(auto [v,c]:adj[u]){
            if(!vis[v]){
                vis[v]=1;
                q.push(v);
            }
        }
    }
    return res/2;
}
int dfs(int u,int fa,int from){
    dep[u]=dep[fa]+1;
    vector<int> vec;
    for(auto [v,idx]:adj[u]){
        if(v==fa)continue;
        if(dep[v]>0){
            //返祖边
            if(dep[u]>dep[v])vec.push_back(idx);
        }
        else{
            int t=dfs(v,u,idx);
            if(t>0)vec.push_back(t);
        }
    }
    while(vec.size()>1){
        int x=vec.back();
        vec.pop_back();
        int y=vec.back();
        vec.pop_back();
        ans.emplace_back(x,y);
    }
    if(vec.size()==1){
        ans.emplace_back(vec[0],from);
        return -1;
    }
    return from;
}
void solve(){
    int n;
    cin>>n;
    for(int i=0;i<=2*n;i++){
        dep[i]=deg[i]=vis[i]=0;
        adj[i].clear();
    }
    ans.clear();
    map<int,int> idL,idR;
    for(int i=1;i<=n;i++)cin>>a[i],idL[i-a[i]]=idR[i+a[i]]=0;
    int cnt=0;
    for(auto &[x,y]:idL)y=++cnt;
    for(auto &[x,y]:idR)y=++cnt;
    for(int i=1;i<=n;i++){
        int x=idL[i-a[i]],y=idR[i+a[i]];
        adj[x].emplace_back(y,i);
        adj[y].emplace_back(x,i);
        deg[x]++;
        deg[y]++;
    }
    for(int i=1;i<=cnt;i++){
        if(!vis[i]){
            if(bfs(i)&1){
                cout<<"No\n";
                return;
            }
            dfs(i,0,-1);
        }
    }
    cout<<"Yes\n";
    for(auto [x,y]:ans)cout<<x<<" "<<y<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    cin>>T;
    while(T--)solve();
    return 0;
}
  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值