ABC_216

传送门

C - Many Balls

思路:

二进制表示

#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
ll n;
int a[100];
string ans;
int main(){
    cin>>n;
    ll t=n,tot=0;
    while(t){
        a[++tot]=t%2;
        t=t/2;
    }
    //for(int i=tot;i>=1;i--) cout<<a[i];
    //cout<<"\n";
    ans="";
    int ok=1;
    for(int i=tot;i>=1;i--){
        if(ok&&a[i]==1) ans+='A',ok=0;
        else if(a[i]==1) ans+="BA";
        else ans+='B';
    }
    cout<<ans<<"\n";
}

D - Pair of Balls

思路:

建图,用$map$标记小球相同颜色的个数进队列,按优先队列$push$进去,跑拓扑序,每次删点要将新的点$push$到队列

代码:
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair <int,int> pll;
const int N=2e5+7;
int n,m,vis[N];
vector <int> ho[N];
vector <int> cnt[N];
int TOPO(){
    priority_queue <pll> q;
    for(int i=1;i<=m;i++){
        int id=ho[i].back();ho[i].pop_back();
        q.push(pll(cnt[id].size(),id));
    }
    int ac=1;
    while(!q.empty()){
        pll it=q.top();q.pop();
        int id=it.se;
 
        if(vis[id]) continue;
        if(cnt[id].size()>1){
            vis[id]++;
            int p1=cnt[id][0],p2=cnt[id][1];
            if(ho[p1].size()){
                int idx=ho[p1].back();ho[p1].pop_back();
                cnt[idx].push_back(p1);
                q.push(pll(cnt[idx].size(),idx));
            }
            if(ho[p2].size()){
                int idx=ho[p2].back();ho[p2].pop_back();
                cnt[idx].push_back(p2);
                q.push(pll(cnt[idx].size(),idx));
            }
        }else{
            ac=0;
            break;
        }
    }
    return ac;
}
 
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int k,x;
        scanf("%d",&k);
        for(int j=1;j<=k;j++){
            scanf("%d",&x);
            ho[i].push_back(x);
        }
        reverse(ho[i].begin(),ho[i].end());
        cnt[ho[i][k-1]].push_back(i);
    }
 
    if(TOPO()) puts("Yes");
    else puts("No");
}

E - Amusement Park

思路:

很容易看出每次都去$A_i$​​​​最大的地方,我们可以先排个序,然后去二分个$x$表示大于等会$x$的要修改至$x$。然后去$check$看还剩多少次数$cnt$,最加上$x+1$​​​的贡献。

代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+7;
int n,k,cnt;
ll a[N];
int check(ll mid){
    ll sum=0;
    for(int i=1;i<=n;i++){
        sum=sum+max(0ll,a[i]-mid+1);
    }
    if(sum<=k){
        cnt=k-sum;
        return 1;
    }else{
        return 0;
    }
}

int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    ll l=0,r=2e9,up=0;
    sort(a+1,a+1+n);reverse(a+1,a+1+n);
    while(l<=r){
        ll mid=(l+r>>1);
        if(check(mid)) up=mid,r=mid-1;
        else l=mid+1;
    }
    ll res=0;
    for(int i=1;i<=n;i++){
        ll t=max(0ll,a[i]-up+1);
        if(i<=cnt){
            if(up>0) res=res+(a[i]+up-1)*(t+1)/2ll;
            else res=res+(a[i]+up)*t/2ll;
        }else{
            res=res+(a[i]+up)*t/2ll;
        }
    }
    cout<<res<<"\n";
}

F - Max Sum Counting

思路:

可以看出这题其实是个背包问题,在前$i$​​​​个中​​​​必选$B_i$​的贡献就是答案

代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N=5010;
const int mod=998244353;
struct node{int v,w;}a[N];
int n,f[N][N];
bool cmp(node x,node y){return x.v<y.v;}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i].v);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i].w);
    }
    sort(a+1,a+1+n,cmp);
    f[0][0]=1;
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=5000;j++){
           if(a[i].w<=j){
                f[i][j]=(f[i-1][j]+f[i-1][j-a[i].w])%mod;
                if(j<=a[i].v) ans=(ans+f[i-1][j-a[i].w])%mod;
            }else{
                f[i][j]=(f[i][j]+f[i-1][j])%mod;
            }
        }
    }
    printf("%d\n",ans);
}

G - 01Sequence

思路:

可以看出,我们应该贪心的将$1$​​​的数量放在最后面开始放,我们可以用树状数组在实现区间查询修改,用并查集来支持从最右边开始单点修改的位置。

代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2e5+7;
struct node{int l,r,x;}a[N];
int n,m,Ans[N],tr[N],f[N];
int lowbit(int i){return i&(-i);}
int fin(int x){return f[x]==x? x:f[x]=fin(f[x]);}
void add(int x){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]++;
}
int ask(int x){
    int sum=0;
    for(int i=x;i;i-=lowbit(i)) sum=sum+tr[i];
    return sum;
}
bool cmp(node x,node y){
    return x.r<y.r;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>a[i].l>>a[i].r>>a[i].x;
    }
    for(int i=0;i<=n;i++) f[i]=i;
    sort(a+1,a+1+m,cmp);
    for(int i=1;i<=m;i++){
        int lim=a[i].x-(ask(a[i].r)-ask(a[i].l-1));
        for(int j=1;j<=lim;j++){
            int u=fin(a[i].r);
            add(u),Ans[u]=1;
            f[u]=fin(u-1);
        }
    }
    for(int i=1;i<=n;i++) cout<<Ans[i]<<" ";
    cout<<"\n";
}

ABC_127

传送门

D - Cutting Woods

思路:

我们可以反过来思考下,他将木棒则断,意思就是将集合一分为二,然后查询就是询问集合大小,那我们就可以离线处理下,然后反过来询问,就是合并集合,可用并查集维护,然后数据比较大要离散化处理。

ps:貌似multiset可以直接写

代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N=2e5+7;
struct node{int c,x;}q[N];
int n,m;
int a[N],cnt[N],f[N];
int fin(int x){return f[x]==x? x:f[x]=fin(f[x]);}
vector <int> Ans,ho;
void bing(int x,int y){
    int f1=fin(x),f2=fin(y);
    if(f1==f2) return;
    f[f2]=f1,cnt[f1]+=cnt[f2];
}
int get(int x){return lower_bound(ho.begin(),ho.end(),x)-ho.begin()+1;}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>q[i].c>>q[i].x;
        if(q[i].c==1){
            ho.push_back(q[i].x);
        }
    }
    ho.push_back(n);
    sort(ho.begin(),ho.end());
    ho.erase(unique(ho.begin(),ho.end()),ho.end());
    int idx=1;
    for(int i=0;i<ho.size();i++){
        f[i+1]=i+1;
        cnt[i+1]=(ho[i]-idx+1);
        idx=ho[i]+1;
    }
    for(int i=m;i>=1;i--){
        if(q[i].c==1){
            bing(get(q[i].x),get(q[i].x)+1);
        }else{
            Ans.push_back(cnt[fin(get(q[i].x))]);
        }
    }
    reverse(Ans.begin(),Ans.end());
    for(auto p:Ans) cout<<p<<"\n";
 
}

E - Sorting Queries

思路:

用队列和multiset模拟处理即可。

代码:
#include <iostream>
#include <set>
#include <queue>
using namespace std;
int m;
multiset <int> sa;
queue <int> q;
int main(){
    cin>>m;
    int cnt=0,ch=0;
    while(m--){
        int op,x;
        cin>>op;
        if(op==1){
            cin>>x;
            q.push(x);
            ++ch;
        }else if(op==2){
            if(cnt){
                auto it=sa.lower_bound(-1);
                cout<<*it<<"\n";
                cnt--;
                sa.erase(it);
            }else{
                int p=q.front();q.pop();
                cout<<p<<"\n";
                ch--;
            }
        }else{
            cnt=cnt+ch;
            ch=0;
            while(!q.empty()) sa.insert(q.front()),q.pop();
        }
    }
}

F - Make Pai

思路:

状态划分:$dp[i][j]$​为i到j配对完的的方案数

我们让i去找个k配对,那么其中间的一定也要配对完才能选[i+1,k-1],即可 将其划分[i+1,k-1]和[k+1,j]两个集合,然后进行状态转移。

状态转移:$dp[i][j]=dp[i+1][k-1]*dp[k+1][j]*C_{x+y+1}^{x}$​​其中$x=k-i-1>>1,y=j-k>>1$​。​​​​​​​

$C_{j-i+1}^{j-k+1}$​​​​​​​​,的意义是两个集合中有x和y+1个配对放x+y+1个位置的方案。

代码:
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int N=410;
int n,m,e[N][N];
ll f[N][N],fac[2000010];
ll ksm(ll x,ll p){
    ll res=1;
    while(p){
        if(p%2==1) res=res*x%mod;
        p/=2;
        x=x*x%mod;
    }
    return res;
}
ll inv(ll a) {
    return ksm(a,mod-2)%mod;
}
ll C(ll n,ll k){
    if(k>n)return 0;
    if(k==1)return n;
    return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
ll dfs(int l,int r){
    if(l>r) return 1;
    if(f[l][r]!=-1) return f[l][r];
    ll ans=0;
    for(int i=l+1;i<=r;i+=2){
        int x=i-l-1>>1,y=r-i>>1;
        if(!e[l][i]) continue;
        ans=(ans+1ll*dfs(l+1,i-1)*dfs(i+1,r)%mod*1ll*C(x+y+1,y)%mod)%mod;
    }
    return f[l][r]=ans;
}
int main(){
    cin>>n>>m;
    memset(f,-1,sizeof f);
    fac[0] = 1;
    for(int i = 1;i < 2000006; i++) {
        fac[i] = (fac[i-1]*i)%mod;
    }
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        e[u][v]=e[v][u]=1;
    }
    cout<<dfs(1,2*n)<<"\n";
}

G - Groups

思路:

这题分组,我们可以向推斯特林数的递推一样推这题。

状态划分:$f[i][j]$为1-i个人合法划分j组的方案。

当一个人要进入分组的时候,他可以单独分为一组,或者可以放入前面合法组中。

状态转移:$f[i][j]=f[i-1][j-1]+f[i-1][j]*(j-\frac{i-1}{m})$​​

代码:
#include <iostream>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int N=5010;
int n,m;
int f[N][N];
int main(){
    scanf("%d %d",&n,&m);
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            f[i][j]=(f[i-1][j-1]+1ll*f[i-1][j]*(j-(i-1)/m)%mod)%mod;
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d\n",f[n][i]);
    }
}

ABC_218

传送门

D - Rectangles

我们可以按$x_i$​排个序,然后和扫描线一样从前往后扫,用个$map$标记$y_i$相等的点,然后计算答案。

代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int N=2010;
int n;
int x[N],y[N],ma[N];
vector <int> ho,G[N];
int get(int x){
    return lower_bound(ho.begin(),ho.end(),x)-ho.begin()+1;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d %d",&x[i],&y[i]);
        ho.push_back(x[i]);ho.push_back(y[i]);
    }
    sort(ho.begin(),ho.end());
    ho.erase(unique(ho.begin(),ho.end()),ho.end());
    for(int i=1;i<=n;i++){
        x[i]=get(x[i]);
        y[i]=get(y[i]);
        G[x[i]].push_back(y[i]);
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(auto p:G[i]){
            ma[p]++;         
        }
        for(int j=1;j<=i-1;j++){
            int cnt=0;
            for(auto p:G[j]){
                if(ma[p]) ans=ans+cnt,cnt++;
            }
        }
        for(auto p:G[i]){
            ma[p]--;
        }
    }
    printf("%lld\n",ans);
}

E - Destruction

思路:

有负边的最小生成树,负边必选,然后跑最小生成树。

代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=2e5+7;
struct node{
    int u,v,w;
    bool operator<(const node &t)const{
        return w<t.w;
    }
}e[N];
int n,m,f[N];
int fin(int x){return f[x]==x? x:f[x]=fin(f[x]);}
int bing(int x,int y){
    int f1=fin(x),f2=fin(y);
    if(f1==f2) return 0;
    f[f2]=f1;
    return 1;
}
 
int main(){
    scanf("%d %d",&n,&m);
    ll sum=0;
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);
        sum=sum+e[i].w;
    }
    sort(e+1,e+1+m);
    for(int i=1;i<=n;i++) f[i]=i;
    ll ans=0;
    for(int i=1;i<=m;i++){
        if(bing(e[i].u,e[i].v)||e[i].w<=0){
            ans=ans+e[i].w;
        }
    }
    printf("%lld\n",sum-ans);
}

F - Blocked Roads

思路:

首先bfs跑个最短路,标记最短路,询问时如果不是标记的最短路,则ans为最短路,否则跑bfs再跑最短路。因为最短路最长为n,所以时间复杂度为$log(nm)$

代码:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int N=500;
int n,m,s[N*N],d[N*N],f[N],pre[N],vis[N];
int e[N][N];
vector <int> G[N];
int bfs(){
    queue <int> q;
    for(int i=1;i<=n;i++) f[i]=0,vis[i]=0;
    q.push(1);vis[1]=1;
    while(!q.empty()){
        int p=q.front();q.pop();
        for(auto v:G[p]){
            if(!vis[v]){
                f[v]=f[p]+1;
                vis[v]=1;
                pre[v]=p;
                q.push(v);
            }
        }
    }
    if(f[n]==0) return -1;
    else return f[n];
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        s[i]=u,d[i]=v;
        e[u][v]=1;
        G[u].push_back(v);
    }
    int ans=bfs();
    int ne=n;
    while(ne>=1){
        int t=pre[ne];
        e[t][ne]=0;
        ne=t;
    }
    for(int i=1;i<=m;i++){
        if(e[s[i]][d[i]]){
            printf("%d\n",ans);
            continue;
        }
        for(int j=0;j<G[s[i]].size();j++)
            if(G[s[i]][j]==d[i]){
                G[s[i]].erase(G[s[i]].begin()+j);
                break;
            }

        printf("%d\n",bfs());
        G[s[i]].push_back(d[i]);
    }
}

G - Game on Tree 2

思路:

首先中位数为跟到叶子节点的树链的中位数, 我们能用主席树算出,然后根据中位数来树形dp,深度为奇数则是Alice操作选中位数最大,偶数为BOb操作选中位数最小。

代码
#include <iostream>
#include <vector>
#include <algorithm>
#define mid (l+r>>1)
using namespace std;
const int N=1e5+7;
int n,tot,a[N],rt[N],f[N];
struct node{int lc,rc,v;}tr[40*N];
vector <int> G[N],ho;
int get(int x){return lower_bound(ho.begin(),ho.end(),x)-ho.begin()+1;}
void insert(int &k,int pr,int l,int r,int p){
    k=++tot;tr[k]=tr[pr],tr[k].v++;
    if(l==r) return;
    if(p<=mid) insert(tr[k].lc,tr[pr].lc,l,mid,p);
    else insert(tr[k].rc,tr[pr].rc,mid+1,r,p);
}
int ask(int k,int l,int r,int key){
    if(l==r) return l;
    int tem=tr[tr[k].lc].v;
    if(tem>=key) return ask(tr[k].lc,l,mid,key);
    else return ask(tr[k].rc,mid+1,r,key-tem);
}
void dfs(int p,int fa,int dep){
    insert(rt[p],rt[fa],1,n,get(a[p]));
    int ok=1;
    if(dep&1) f[p]=0;
    else f[p]=1e9;
    for(auto v:G[p]){
        if(v==fa) continue;
        dfs(v,p,dep+1);
        ok=0;
        if(dep&1){
            f[p]=max(f[p],f[v]);
        }else{
            f[p]=min(f[p],f[v]);
        }
    }
    if(ok){
        if(dep&1){
            f[p]=ho[ask(rt[p],1,n,(dep+1)/2)-1];
        }else{
            f[p]=(ho[ask(rt[p],1,n,(dep+1)/2)-1]+ho[ask(rt[p],1,n,(dep+1)/2+1)-1])/2;
        }
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        ho.push_back(a[i]);
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    ho.erase(unique(ho.begin(),ho.end()),ho.end());
    sort(ho.begin(),ho.end());
    dfs(1,0,1);
    printf("%d\n",f[1]);
}

ARC_125

传送门

A - Dial Up
思路:

他选择一定在$01$之间摇摆,否则不能组成,我们可以大胆的dfs去搜索。

代码:
#include <iostream>
using namespace std;
const int N=2e5+7;
int n,m;
int a[N],b[N];
int dfs(int p1,int p2){
    if(p2==m) return 0;
    int ans=-1e9;
    for(int i=0;i<=n-1;i++){
        if(b[p2]==a[(p1+i)%n]){
            ans=dfs((p1+i)%n,p2+1)+1+i;
            break;
        }else if(b[p2]==a[(p1-i+n)%n]){
            ans=dfs((p1-i+n)%n,p2+1)+1+i;
            break;
        }
    }
    return ans;
}
void solve(){
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<m;i++) scanf("%d",&b[i]);
    int ans=dfs(0,0);
    if(ans>=0) printf("%d\n",ans);
    else puts("-1");

}
int main(){
    solve();
}

B - Squares

思路:

如题要求$x^2-y=z^2$​,$z$​的个数,我们将其化为:$x^2-z^2=y$​,则求的是平方数之间的差值有多少不同的情况,易知差值其为等差数列,枚举平方数之间的间隔,通过公式来计算答案。

代码:
#include <iostream>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll n;
int main(){
    cin>>n;
    ll ans=0;
    ll s=0;
    for(ll i=1;;i++){
        s+=2;
        if(n-(i*i-s)<0) break;
        ans=(ans+(n-(i*i-s))/s)%mod;
    }
    printf("%lld\n",ans);
}

C - LIS to Original Sequence
思路:

题目给出$LCS$​​,我们可以得出在$A_i$​​到$A_{i+1}$​之间肯定是递减的序列,且不属区间$[A_i,A_{i+1}]$​,这样保证了$LCS$不会增大。然后题目要构造字典序最小,我们就将能放在$A_i,A_{i+1}$​之间的数放一个最小的放在总结,其余的以降序放在$A_n$之后

代码:
#include <iostream>
using namespace std;
const int N=2e5+7;
int k,n,a[N],B[N];
int main(){
    cin>>n>>k;
    for(int i=1;i<=k;i++) cin>>a[i];
    int j=1;
    for(int i=1;i<k;i++){
        printf("%d ",a[i]);
        B[a[i]]=1;
        while(B[j]) j++;
        if(j<a[i]){
            printf("%d ",j);
            B[j]=1;
            while(B[j]) j++;
        }
    }
    for(int i=n;i>=1;i--) if(!B[i]) printf("%d ",i);
    puts("");
}
最后修改:2023 年 01 月 10 日
如果觉得我的文章对你有用,请随意赞赏