ABC337 题解

前言:

提交洛谷题解的部分会详细一些。

A

题意

比较两人总得分,判断胜者或平局。

Sol

字面意思模拟即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,a[N],m,k;
ll f[N],res,sum,ans;
string s;
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    ll x=0,y=0;
    for(int i=1;i<=n;i++){
        cin>>x>>y;
        res+=x;
        res-=y;
    }
    if(res>0)cout<<"Takahashi";
    if(res==0)cout<<"Draw";
    if(res<0)cout<<"Aoki";
    return 0;
}

B

题意

判断字符串是否含有逆序对( \(B\) 后面有 \(A\)\(C\) 后面有\(A\)\(B\) )。

Sol

字面意思模拟即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,a[N],m,k;
ll f[N],res,sum,ans;
string s;

int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>s;
    n=s.size();
    s=" "+s;
    ll a=0,b=0,c=0;
    for(int i=1;i<=n;i++){
        if(s[i]=='A'){
            if(b||c){
                cout<<"No\n";
                return 0;
            }
            a=1;
        }
        if(s[i]=='B'){
            if(c){
                cout<<"No\n";
                return 0;
            }
            b=1;
        }
        if(s[i]=='C'){
            c=1;
        }
    }
    cout<<"Yes\n";
    return 0;
}

C

题意

给定 \(n\) 个人每个人前面是谁,还原排队的顺序。

Sol

记录每个人后面跟着谁,从对头开始递归找即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 400005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,a[N];
string s;

int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    ll l=0;
    for(int i=1;i<=n;i++){
        ll x;
        cin>>x;
        if(x==-1)l=i;
        else{
            a[x]=i;
        }
    }
    while(a[l]){
        cout<<l<<" ";
        l=a[l];
    }
    cout<<l<<" ";
    return 0;
}

D

题意

一个 \(n\times m\) 矩阵,选取一个 \(k\times1\)\(1\times k\) 的区域,这个区域不能含有 x,找到一个含有最少 . 的区域并输出这个最小值,或报告无解。

Sol

. 值设为 \(1\),把 o 值设为 \(0\),把 x 值设为 inf ,把列和行单独分开来做,动态查前缀和,即求 \(sum_i-sum_{i-k}\),如果这东西绝对值比 \(k\) 小,说明是一种合法答案。

但题目没有规定 \(n\)\(m\) 的单独范围,直接开二维存图会炸。一种解决方式是用 vector。这里介绍一种另类的方式。

行的情况很好维护,读入时顺带处理即可,当然也可以用下面这种。

重点在列,先考虑正常前缀和,\(w_{i,j}\) 维护前 \(i\) 行,第 \(j\) 列的前缀和,然后求的是 \(w_{i,j}-w_{i-k,j}\),注意到每次查询区间长度固定,很像滑动窗口,只需要维护前 \(k\) 行位置的答案即可,那么就可以去掉一维,\(w_j\) 表示第 \(j\)\(i-k+1 \sim i\) 位置的和,用一个队列存每行的字符串,动态删除超过位置的前缀即可。

具体实现见代码,复杂度 \(O(nm)\)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 400005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e9;
const double eps=1e-6;
ll n,m,k;
ll sum[N],w[N];
queue<string>q;//好似string内部是vector所以不会炸
string s;
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n>>m>>k;
    ll res=inf;
    for(int i=1;i<=n;i++){
        cin>>s;
        s=" "+s;
        q.push(s);
        for(int j=1;j<=m;j++){
            sum[j]=sum[j-1];//这个是行的
            if(s[j]=='x')sum[j]+=inf,w[j]+=inf;
            if(s[j]=='.')sum[j]++,w[j]++;//行加,列也要加
            if(abs(sum[j]-sum[j-k])<=k&&j>=k)res=min(res,sum[j]-sum[j-k]);//维护行的答案
        }
        if(i>=k){
            s=q.front();
            q.pop();
            for(int j=1;j<=m;j++){
                if(abs(w[j])<=inf)res=min(res,w[j]);//维护列的答案
                if(s[j]=='x')w[j]-=inf;//新增一列就得删除一列
                if(s[j]=='.')w[j]--;
            }
        }
    }
    if(res==inf)res=-1;
    cout<<res;
    return 0;
}

E

题意

交互题,\(n\) 瓶果汁其中一瓶有毒,可以给任意个人喝任意数量的果汁,确定有毒的那一瓶,并最小化喝果汁的人数。

第一次输入,给定 \(n\)
输出最小喝药水人数 \(m\)
以及对应每个人喝了多少瓶果汁,以及喝了哪些果汁。

第二次输入,根据你分配喝果汁的情况,返回谁会中毒。
输出中毒的是哪一瓶。

Sol

每个人每瓶果汁只有喝和不喝两种状态,这本质上就是二进制。用二进制表示每瓶果汁的饮用情况,这样就只需要 \(\lceil \log n \rceil\) 名朋友就能完成试毒。

更具体的说,第 \(i\) 瓶果汁的饮用情况拆成 \(i-1\) 对应的二进制表示,(\(0\) 也能用到,不能浪费),第 \(j\) 位为 \(1\) 表示第 \(j\) 个人喝了这瓶果汁。

手搓几组数据试一试,结果如下。
1:

0

2:

01

3:

010
001

4:

0101
0011

5:

01010
00110
00001

6:

010101
001100
000011

7

0101010
0011001
0000111

8

01010101
00110011
00001111

结果返回的是那些人中毒了,把这些中毒的人所在位置代表的二进制相加,得到有毒果汁的唯一s编号。

Code

#include<bits/stdc++.h>
#define ll int
#define N 205
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,lg[N];
string s;
ll f[N][N],sum[N];
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    ll t=__lg(n-1)+1;
    cout<<t<<endl;
    ll fl=0,q=0,now=1;;
    for(ll i=1;i<=t;i++){
        fl=0,q=0;
        for(ll j=1;j<=n;j++){
            f[i][j]=fl;
            q++;
            if(q==now){
                q=0;
                fl^=1;
            }
            sum[i]+=f[i][j];
        }
        now*=2;
    }
    for(ll i=1;i<=t;i++){
        cout<<sum[i]<<" ";
        for(ll j=1;j<=n;j++){
            if(f[i][j])cout<<j<<" ";
        }
        cout<<endl;
    }
    fflush(stdout);
    cin>>s;
    ll res=0;
    for(ll i=0;i<t;i++)if(s[i]=='1')res+=(1<<i);
    cout<<res+1<<endl;
    fflush(stdout);
    return 0;
}
//
//01001011
//00101101
//00010111

F

题意

\(n\) 个物品构成一个环,从第 \(p\) 个物品开始顺时针拿,拿到 \(p-1\) 个物品。每次将一个物品放到盒子当中,总共 \(m\) 个盒子,一个盒子中最多放 \(k\) 个物品。优先将同类型的物品放到一个盒子当中,如果放满了则向后新找一个空盒子,如果没有空盒子就扔掉这个物品,注意,一个盒子当中只能放一种类型的物品。求盒子中的球的数量。

求所有 \(p\) 的答案。

Sol

先断环为链,复制一份变成 \(2n\) 个物品。然后动态维护固定区间长度,左右端点答案。给每个类型开个桶 \(f_{a_i}\) 存区间数量。

右端点向右新增物品,加上当前物品的影响。如果 \(f_{a_i} \equiv 1 \ \left(\mod k \ \right)\),说明需要新开一个盒子,如果盒子数量比 \(m\) 小,那么答案增加 \(\min (k,sum_{a_i}-f_{a_i}+1)\),很好理解,如果剩下的物品数比 \(k\) 多,这个盒子只能放 \(k\) 个,比 \(k\) 少,只能放总个数减去已经放进去的个数。如果盒子数量大于等于 \(m\),就不能再放新的物品,那就不管了直接扔掉。

左端点向右删去物品,删去上个物品的影响。减去数量,然后如果 \(f_{a_i} \equiv 0 \ \left(\mod k \ \right)\) 盒子数量减一,答案减去删去盒子的物品数量。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 500005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,k;
ll a[N],f[N],sum[N];

int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum[a[i]]++;
        a[i+n]=a[i];
    }
    ll cnt=0,j=1,ans=0;
    for(int i=1;i<=n;i++){
        while(cnt<m&&j<i+n){
            f[a[j]]++;
            if((f[a[j]]-1)%k==0){
                cnt++;
                ans+=min(k,sum[a[j]]-f[a[j]]+1);
            }
            j++;
        }
        cout<<ans<<endl;
        f[a[i]]--;
        if((f[a[i]])%k==0){
            cnt--;
            ans-=min(k,sum[a[i]]-f[a[i]]);
        }
    }
    return 0;
}

G

题意

一颗 \(n\) 个节点的树,求所有 \(u \in [1,n]\),以 \(u\) 为根时,求点对 \((v,w)\) 数,满足 \(v< w\)\(w\)\(u \rightarrow v\) 的路径上。

Sol

就如题目所说的,采用 换根 \(dp\)。这里先规定一些名称。

\(f_i\) 表示以 \(i\) 为根的子树的答案。

\(a_i\) 表示以 \(i\) 为根的子树内小于 \(\large {i}\) 的节点数量。

\(h_i\) 表示以 \(i\) 为根的子树内小于 \(\large farher_{i}\) 的节点数量。

\(g_i\) 表示以 \(i\) 为根的的答案。

根据换根 \(dp\) 惯例,先考虑钦定 \(1\)为根节点,求出 \(f_{1}\)

由题目描述,不难找出关系式。

\[x \text{ 子树答案}=x \text{ 子树内小于 }x \text{ 的节点数量}+x \text{ 儿子们的子树答案} \]

也就是:

\[f_x=a_x+\sum_{y\in son_x} f_y \]

\(g_1=f_1\)

接着考虑换根。

原来以 \(x\) 为根,现在要把 \(y\) 换成根,维护换根后 \(g_y\) 基于 \(g_x\) 的变化。

红色部分原来是 \(x\) 的子树,换根后要减去这部分对 \(x\) 的贡献,也就是减去 \(y\) 子树内小于 \(x\) 的结点数,即减去 \(h_y\)

蓝色部分原来不是 \(y\) 的子树,换根后要加上这部分对 \(y\) 的贡献,也就是加上 \(y\) 子树外小于 \(y\) 的节点数,小于 \(y\) 的节点树只有 \(y-1\) 个,子树内占了 \(a_y\) 个,所以要加上 \(y-1-a_y\)

所以:

\[g_y=g_x+(y-1-a_y)-h_y \]

到这里推到出所有公式都比较简单,那么如何得到 \(a_i\)\(h_i\) 呢?

类似前缀和的思想,记录递归 \(x\) 子树之前的答案,以及递归 \(x\) 子树之后的答案,后者减去前者即可,用树状数组或线段树都可以做。

当然也可以可持久化

时间复杂度 \(O(nlogn)\)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 500005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n;
ll f[N],g[N],a[N],h[N],tr[N];
//ai:以i为根的子树内比i小的数的个数
//fi:以i为根的子树的答案
//fu+=fv+au
//gv=gu-fv
vector<ll>v[N];
void add(ll x,ll t){
    for(int i=x;i<=n;i+=(i&(-i)))tr[i]+=t;
}
ll qr(ll x){
    ll ans=0;
    for(int i=x;i>0;i-=(i&(-i)))ans+=tr[i];
    return ans;
}
void dfs1(ll x,ll fa){
    a[x]=-qr(x-1);
    h[x]=-qr(fa-1);  
    //要在查完fa后再加入x,否则会导致x<fa的情况无法被统计到
    for(auto y:v[x])if(y!=fa)dfs1(y,x);
    add(x,1);
    a[x]+=qr(x-1);
    h[x]+=qr(fa-1);
    f[x]=a[x];
    for(auto y:v[x])if(y!=fa)f[x]+=f[y];
}
void dfs2(ll x,ll fa){
    for(auto y:v[x]){
        if(y==fa)continue;
        g[y]=g[x]-h[y]+(y-a[y]-1);
        dfs2(y,x);
    }
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    for(int i=1,x,y;i<n;i++){
        cin>>x>>y;
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs1(1,0);
    g[1]=f[1];
    dfs2(1,0);
    for(int i=1;i<=n;i++)cout<<g[i]<<" ";
    return 0;
}

posted @ 2024-01-26 12:01  yshpdyt  阅读(13)  评论(0编辑  收藏  举报