iwtgm-32

发布时间 2023-12-02 14:41:54作者: WW爆米花

Codeforces Round 911 (Div. 2)

A.

注意它是动态补水的,样例看了半天才明白
所以只要连续有大等于三个空格,花费就是2(第一个和第三个空格人为灌水)
第二个空格的水可以无限续,那其他空格就都从这里搬运
若没有连续大等于三个的空格,花费就是所有空格的数量和

void solve() {
    int n;cin>>n;
    string s;cin>>s;
    int sum=0;
    int tmp=0;
    int ma=-1;
    for(int i=0;i<s.size();i++){
        if(s[i]=='.')tmp++;
        else if(s[i]=='#'){
            sum+=tmp;
            ma=max(ma,tmp);
            tmp=0;
        }
    }
    if(tmp){
        ma=max(ma,tmp);
        sum+=tmp;
    }
    if(ma>=3){
        cout<<2<<endl;
    }else{
        cout<<sum<<endl;
    }
}

B.

假设我要留下1,那么2和3全部都要被消去
如果2和3数量相同,那么每次消除它们俩即可
如果数量不同,若数量差为1(题目保证每个数的数量至少为1)
那么消去min(2的数量,3的数量),剩一个,而我必须要消去它,若我花费一个1和这个数一起消去,又会增加另一个我不需要的数,一直循环,我不可能只剩下1
若数量差为2,先消去min(2的数量,3的数量),剩两个,我花费一个1和这个数一起消去,增加了另一个我不需要的数,然后再把两个不需要的数消去,会生成一个我要的数,那么最后可以只剩1
继续举例推广,会发现数量差为奇数,只剩1是不可能的;数量差为偶数,可以只剩1

void solve() {
    int a,b,c;cin>>a>>b>>c;
    if(abs(b-c)%2==0){
        cout<<1<<' ';
    }else cout<<0<<' ';
    if(abs(a-c)%2==0){
        cout<<1<<' ';
    }else{
        cout<<0<<' ';
    }
    if(abs(a-b)%2==0){
        cout<<1;
    }else cout<<0;
    cout<<endl;
}

C.

先想从叶节点回搜,t了,同时非常没必要
直接dfs一遍就好
在dfs的过程中,路线与节点字符不同的话就cnt++(回溯记得消掉影响,详见代码)
到叶节点就比较cnt和ans取最小值

int n;
struct node{
    int l,r;
}tree[N];
int ans=inf;
string s;
int cnt;
void dfs(int u){
    if(tree[u].l==0&&tree[u].r==0){
        //cout<<"cnt="<<cnt<<endl;
        ans=min(ans,cnt);
        return ;
    }
    if(tree[u].l){
        if(s[u]!='L')cnt++;
        dfs(tree[u].l);
        if(s[u]!='L')cnt--;
    }
    if(tree[u].r){
        if(s[u]!='R')cnt++;
        dfs(tree[u].r);
        if(s[u]!='R')cnt--;
    }
}
void solve() {
    cin>>n;
    for(int i=1;i<=n;i++){
        tree[i].l=0;tree[i].r=0;
    }
    cin>>s;s='C'+s;
    for(int i=1,l,r;i<=n;i++){
        cin>>l>>r;
        if(l!=0)tree[i].l=l;
        if(r!=0)tree[i].r=r;
    }
    ans=inf;
    cnt=0;
    dfs(1);
    cout<<ans<<endl;
}

D.

飞升题,一些很巧妙但自己想不到的题
前置:1e5的因子数只有100来个,预处理出每个数的因子数组
有一个贡献数组,贡献本来是本身,如果它有因数,那么贡献要-因数的贡献(因为因数的贡献计算过了,所以减去),例6,它的贡献是6-1-2-3
原数组排序后不影响答案,先从小到大排序
前面两个较小数确定后,后面最大数都可以选可直接计数
精髓看代码注释

int val[N];
vector<int>yin_zi[100010];
int cnt[N];
int a[N];
void solve() {
    memset(cnt,0,sizeof(cnt));
    int n;
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    sort(a,a+n);
    ll ans=0;
    for(int i=0;i<n-1;i++){
        ll sum=0;
        for(auto it:yin_zi[a[i]]){
            sum+=1ll*val[it]*cnt[it];//cnt[it]it是这个数的因子,cnt[it]表示已出现的这个因子的个数*因子的贡献
        }                                  //一个新的数加进来,它与之前的任何数都有贡献(至少为1)。
                                           //正常我们需要知道两个确定的数,才能知道它们的最大公因数是多少,那么在不知道确定的数的情况下该如何确定最大公因数呢
                                           //我们加进一个数后,会把它所有的因子的个数+1
                                           //回到问题,我们确实无法直接求出最大公因数,所以我们用的枚举+更新,因为我们的贡献是预处理好了的(如上面举的6的例子)
                                           //cnt存的是已经出现过的因子的个数嘛,我枚举当前数的因子,如果出现过,就+相应贡献
                                           //如4,8,枚举8的因子,有1,并且4的因子也有1,加上1的贡献
                                           //有2,并且4的因子也有2,加上2的贡献
                                           //有4,并且4的因子也有4,加上4的贡献
                                           //注意贡献预处理过,所以贡献没有重复计算
                                           //思想也就是,最终其实我们也不知道数两两之间最大的公因数是什么,只是说去枚举,有,就更新,没有就不管,其实贡献就算完了
        ans+=sum*(n-i-1);//后面的大数随便选
        for(auto it:yin_zi[a[i]])cnt[it]++;//更新已存在的因子个数
    }
    cout<<ans<<endl;
}
 
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int ww_=1;
    //cin>>ww_;
    for(int i=1;i<=100000;i++){
        val[i]=i;//初始贡献是本身
        yin_zi[i].push_back(i);
    }
    for(int i=1;i<=100000;i++){
        for(int j=i*2;j<=100000;j+=i){
            yin_zi[j].push_back(i);
            val[j]-=val[i];//该数-除本身外的所有因子的贡献,目的是避免重复计算贡献
        }
    }
    cin>>ww_;
    while(ww_--){
        solve();
    }
    return 0;
}

E.

对于简单路径,这个操作并不会更优
那么考虑复杂一点的环,已知换上任意两点可互相到达,那么也就是环上任意一点可到达的非环上的点,环上其他点也能都连一条边到这个点
也就是说一个强连通分量的任一点,可以到达另一强连通分量的任一点(注意单独一点是一个强连通分量)
那么就可以缩点了,这里用tarjan找强连通分量
缩完点,图就简单了许多
然后逆拓扑序dp找最长简单路径的最小值
剩余注释见代码

int n , m;
LL a[N];
vector<int>g[N];
int dfn[N] , low[N] , in_stack[N] , tot = 0 , cnt = 0, belong[N];
stack<int>stk;
int s[N],len;
pair<LL,LL>dp[N];
void tarjan(int x){
    dfn[x]=low[x]=++tot;//初始化
    s[++len]=x;//x入栈
    in_stack[x]=1;//标记已入栈
    for(int i=0;i<g[x].size();i++){
        int y=g[x][i];
        if(!dfn[y]){
            tarjan(y);//先dfs
            low[x]=min(low[x],low[y]);//回溯更新最小时间戳
        }else {
            if(in_stack[y])low[x]=min(low[x],low[y]);//在栈中,更新最小时间戳
        }
    }
    if(dfn[x]==low[x]){//时间戳等于最小时间戳,说明它是最强联通分量的根
        cnt++;//强联通数+1
        LL ch=0,sum=0;
        dp[cnt]={0,0};
        while(s[len]!=x){//同一强连通分量中的点
            belong[s[len]]=cnt;//该点属于这个强连通分量
            in_stack[s[len]]=0;//及时标记不在栈中了,是图,点的入度可能不为1
            int v=s[len];
            ch++;sum+=a[v];
            for(int w:g[v]){
                if(belong[w] != cnt && belong[w] != 0){//不在同一个强连通分量且是一个被搜过的强连通分量
                    if(dp[belong[w]].x > dp[cnt].x){//因为已经缩点了,所以在该分量里选一个最大的
                        dp[cnt] = dp[belong[w]];
                    }
                    else if(dp[belong[w]].x == dp[cnt].x && dp[belong[w]].y < dp[cnt].y){
                        dp[cnt] = dp[belong[w]];
                    }
                }
 
            }
            len--;//栈长度-1
        }
        belong[s[len]]=cnt;//该点属于这个强连通分量
        in_stack[s[len]]=0;//及时标记不在栈中了,是图,点的入度可能不为1
        int v=s[len];
        ch++;sum+=a[v];
        for(int w:g[v]){
            if(belong[w] != cnt && belong[w] != 0){
                if(dp[belong[w]].x > dp[cnt].x){
                    dp[cnt] = dp[belong[w]];
                }
                else if(dp[belong[w]].x == dp[cnt].x && dp[belong[w]].y < dp[cnt].y){
                    dp[cnt] = dp[belong[w]];
                }
            }
 
        }
        len--;//栈长度-1
        dp[cnt].x += ch;
        dp[cnt].y += sum;
    }
}
void init(int n){
    tot = cnt = 0;
    for(int i = 0 ; i <= n ; i ++){
        a[i] = 0 , low[i] = 0 , in_stack[i] = 0 , belong[i] = 0 , dfn[i] = 0 , g[i].clear();len=0;
    }
}
void solve() {
    cin >> n >> m;
    map<pair<int,int>,int>mp;
    for(int i = 1 ; i <= n ; i ++)
        cin >> a[i];
    for(int i = 0 ; i < m ; i ++){
        int x , y;
        cin >> x >> y;
        if(mp.count({x , y}) || x == y){
            continue;
        }
        else{
            g[x].pb(y);
            mp[{x,y}] = 1;
        }
    }
    for(int i = 1 ; i <= n ; i++){
        if(!dfn[i]) tarjan(i );
    }
/*	for(int i = 1 ; i <= n ; i ++){
		cout << bel[i] << endl;
	}*/
    pair<LL,LL>ans = {0 , 0};
    for(int i = 1 ; i <= cnt ; i ++){
        if(dp[i].x > ans.x){
            ans = dp[i];
        }
        else if(dp[i].x == ans.x && dp[i].y < ans.y){
            ans = dp[i];
        }
    }
    init(n);
    cout << ans.x << " " << ans.y << endl;
}

F.

先考虑对全局的询问,注意到一个点是局部最小值(最大值)时,它周围的两个点一定不是,于是每次操作留下来的数不超过原来的一半,直接暴力是O(n)的
现在考虑 (l>1,r<n)的询问,先暴力求出全局的答案,并记录每层剩余的数的位置
对于第一层,发现如果一个位置 不在询问的端点上则这个位置能被保留,当且仅当在对全局操作第一次的时候,这个位置被保留。因为这个位置是否是局部最值仅由它左右两个数决定。之后每一层同理。
于是除了两边的两个点,中间的位置在操作后对应的新序列对应对全局操作后对应的新序列上的一段区间,容易二分找出下一层中新区间的端点(二分的是位置)。
对于两边的点,则视作在一个区间的左右各添加一个数,根据第一段的分析,添加的数和区间的端点至多保留其一。于是每次操作都默认区间左右端点收缩一位,并更新左右添加的数,这样就得到了一个子问题,可以递归处理。
但是当区间长度小于 3时,上面的操作就不成立了,这时直接暴力即可。
注意特判:如果添加的数和端点都不会保留,则将添加的数更新为下一层中对应新区间的对应端点,如果新区间为空,则可以直接在添加的两个数中取出答案。

int n , m;
LL a[N];
vector<int>g[N];
int dfn[N] , low[N] , in_stack[N] , tot = 0 , cnt = 0, belong[N];
stack<int>stk;
int s[N],len;
pair<LL,LL>dp[N];
void tarjan(int x){
    dfn[x]=low[x]=++tot;//初始化
    s[++len]=x;//x入栈
    in_stack[x]=1;//标记已入栈
    for(int i=0;i<g[x].size();i++){
        int y=g[x][i];
        if(!dfn[y]){
            tarjan(y);//先dfs
            low[x]=min(low[x],low[y]);//回溯更新最小时间戳
        }else {
            if(in_stack[y])low[x]=min(low[x],low[y]);//在栈中,更新最小时间戳
        }
    }
    if(dfn[x]==low[x]){//时间戳等于最小时间戳,说明它是最强联通分量的根
        cnt++;//强联通数+1
        LL ch=0,sum=0;
        dp[cnt]={0,0};
        while(s[len]!=x){//同一强连通分量中的点
            belong[s[len]]=cnt;//该点属于这个强连通分量
            in_stack[s[len]]=0;//及时标记不在栈中了,是图,点的入度可能不为1
            int v=s[len];
            ch++;sum+=a[v];
            for(int w:g[v]){
                if(belong[w] != cnt && belong[w] != 0){
                    if(dp[belong[w]].x > dp[cnt].x){
                        dp[cnt] = dp[belong[w]];
                    }
                    else if(dp[belong[w]].x == dp[cnt].x && dp[belong[w]].y < dp[cnt].y){
                        dp[cnt] = dp[belong[w]];
                    }
                }
 
            }
            len--;//栈长度-1
        }
        belong[s[len]]=cnt;//该点属于这个强连通分量
        in_stack[s[len]]=0;//及时标记不在栈中了,是图,点的入度可能不为1
        int v=s[len];
        ch++;sum+=a[v];
        for(int w:g[v]){
            if(belong[w] != cnt && belong[w] != 0){
                if(dp[belong[w]].x > dp[cnt].x){
                    dp[cnt] = dp[belong[w]];
                }
                else if(dp[belong[w]].x == dp[cnt].x && dp[belong[w]].y < dp[cnt].y){
                    dp[cnt] = dp[belong[w]];
                }
            }
 
        }
        len--;//栈长度-1
        dp[cnt].x += ch;
        dp[cnt].y += sum;
    }
}
void init(int n){
    tot = cnt = 0;
    for(int i = 0 ; i <= n ; i ++){
        a[i] = 0 , low[i] = 0 , in_stack[i] = 0 , belong[i] = 0 , dfn[i] = 0 , g[i].clear();len=0;
    }
}
void solve() {
    cin >> n >> m;
    map<pair<int,int>,int>mp;
    for(int i = 1 ; i <= n ; i ++)
        cin >> a[i];
    for(int i = 0 ; i < m ; i ++){
        int x , y;
        cin >> x >> y;
        if(mp.count({x , y}) || x == y){
            continue;
        }
        else{
            g[x].pb(y);
            mp[{x,y}] = 1;
        }
    }
    for(int i = 1 ; i <= n ; i++){
        if(!dfn[i]) tarjan(i );
    }
/*	for(int i = 1 ; i <= n ; i ++){
		cout << bel[i] << endl;
	}*/
    pair<LL,LL>ans = {0 , 0};
    
    init(n);
    cout << ans.x << " " << ans.y << endl;
}