Codeforces Round 906 Div. 1 (CF1889)

发布时间 2023-10-31 21:06:49作者: 樱雪喵

貌似现在发周六的 CF 题解已经失去了时效性,不过问题不大。

A. Qingshan Loves Strings 2

Description

定义一个长度为 \(k\)\(01\)\(s\) 是好的,当且仅当 \(\forall i\in [1,k],s_i\neq s_{k-i+1}\)
现给你一个串,每次操作你可以在任意位置插入一对 \(01\)。请构造操作方案使串变成好的,或判断无解。多组询问。

Solution

div1 A 想半天,鉴定为没救了。

首先发现,一个好的串中 \(0\)\(1\) 的数量一定相等,而插入操作不改变两种字符的数量差。那么如果初始的串 \(0\)\(1\) 数量不等,则无解。
手玩几个小数据,我们猜测其余情况都是有解的。由于将 \(01\) 插在串中间不会改变开头和结尾字符的配对情况,考虑从两端开始配对:

  • 若字符串的开头和结尾不相同,则忽略它们,用同样的方法处理 \([2,k-1]\)
  • 否则根据都是 \(0\) 还是 \(1\) 分讨,在字符串的开头或结尾添加一对 \(01\)

容易发现这样一定可以构造出一种可行解。

Code
int T,n;
string s;
vector<int> ans;
void solve(string s)
{
    // cout<<s<<endl;
    int n=s.size();
    for(int i=0;i<n;i++) if(s[i]==s[n-i-1])
    {
        if(s[i]=='0') 
        {
            ans.push_back(n-i);
            string t;
            for(int j=0;j<n-i;j++) t+=s[j];
            t+="01"; for(int j=n-i;j<n;j++) t+=s[j];
            solve(t); return;
        }
        else
        {
            ans.push_back(i);
            string t;
            for(int j=0;j<i;j++) t+=s[j];
            t+="01"; for(int j=i;j<n;j++) t+=s[j];
            solve(t); return;
        }
    }
}
int main()
{
    T=read();
    while(T--)
    {
        n=read(); cin>>s;
        int cnt=0;
        for(int i=0;i<n;i++) if(s[i]=='1') cnt++;
        if(2*cnt!=n) {printf("-1\n");continue;}
        ans.clear();
        solve(s);
        printf("%d\n",(int)ans.size());
        for(auto x:ans) printf("%d ",x); printf("\n");
    }
    return 0;
}

B. Doremy's Connecting Plan

首先 \(c\) 啥用没有,因为可以移项,令每个点的初始权值为 \(\frac{a_i}{c}\)
自信猜结论。如果第一步 \(x,y\) 能连边,那么 \(1,x\) 或者 \(1,y\) 一定有一个能连边,且这样做是不劣的。
因此最优策略是每个点都跟 \(1\) 连边。式子是 \(s_1+a_i\ge 1\times i\),按 \(i-a_i\) 升序排序贪心即可。

Code
#define int long long
const int N=2e5+5;
int T,n,c;
struct node
{
    int a,w;
    friend bool operator <(const node &x,const node &y) {return x.w<y.w;}
}a[N];
signed main()
{
    T=read();
    while(T--)
    {
        n=read(),c=read();
        for(int i=1;i<=n;i++) a[i].a=read();
        for(int i=2;i<=n;i++) a[i].w=c*i-a[i].a;
        sort(a+2,a+n+1);
        double now=a[1].a;bool flag=1;
        for(int i=2;i<=n;i++)
        {
            if(now<a[i].w) {flag=0;break;}
            now+=a[i].a;
        }
        printf(flag?"Yes\n":"No\n");
    }
    return 0;
}

C1. Doremy's Drying Plan (Easy Version)

Description

给定 \(m\) 条线段 \([l_i,r_i]\),其中 \(l_i,r_i\in [1,n]\)。至多删除 \(k\) 条线段,使 \([1,n]\) 中未被任何线段覆盖的点数最多。输出这个数。
\(n,m\le 2\times 10^5,\, k=2\)

Solution

观察到 \(k\) 只有 \(2\),这意味着如果某点 \(i\)\(\ge 3\) 条线段覆盖,这个点无论如何最后都还是被覆盖,不需要考虑它对答案的贡献。
而一开始就不被覆盖的点也是容易计算的,可以直接加进答案。
那么剩下的点对答案有贡献的情况只分两种:

  • \(i\) 被覆盖了两次,且这两条线段都被删了。
  • \(i\) 被覆盖了一次,且这条线段被删了。

因此被删除的线段也只有两种情况:

  • 如果这两条线段造成了第一类点的贡献,直接枚举每个可能的点,并尝试删除覆盖它的两条线段。
  • 否则,这两条线段造成了两个第二类点的贡献。预处理出每条线段删除的贡献,选最大和次大值。

第二类是容易统计答案的,我们考虑第一类怎么统计。删除两条线段对答案造成的贡献即为恰好只被其中一条覆盖的点数与恰好被它们两个覆盖的点数之和。前者在维护第二类的时候已经求了。而这样的线段至多有 \(n\) 对,可以暴力地维护后者。

求每个点被哪些线段覆盖可以把 \([l,r]\) 拆成 \(l\)\(r+1\)multiset 维护。

C2. Doremy's Drying Plan (Hard Version)

Description

给定 \(m\) 条线段 \([l_i,r_i]\),其中 \(l_i,r_i\in [1,n]\)。至多删除 \(K\) 条线段,使 \([1,n]\) 中未被任何线段覆盖的点数最多。输出这个数。

\(n,m\le 2\times 10^5,\, K\le \min(10,m)\)

Solution

推翻 C1 的做法,考虑 DP。
\(f_{i,j}\) 表示考虑了点 \(i\) 不被覆盖,已经删除了 \(j\) 条线段,前 \(i\) 个点最多不被覆盖的点数。
那么我们枚举上一个没被覆盖的点为 \(k\),考虑所有覆盖的点 \(i\) 的线段 \([l,r]\)

  • 如果 \(l\le k\),由于要保证 \(k\) 没被覆盖,这条线段在 \(k\) 那里就被删了,不应该计入“令点 \(i\) 不被覆盖”的代价。
  • \(l>k\),说明这条线段是因为点 \(i\) 而删掉的,应当计入代价。

设第二类线段数为 \(t\)。那么有转移:

\[f_{i,j}=1+\max\limits_{k=0}^{i-1} f_{k,j-t} \]

直接实现是 \(O(n^2k)\) 的,考虑找性质优化。发现第二维只有 \(K\) 种取值,且 \(j-t\) 的取值随 \(k\) 的增加单调不降。那么我们可以根据 \(j-t\) 的取值将 \(k\) 划分成 \(K\) 段。
对于每个 \(j\) 使用数据结构维护区间最大值即可,但是有同层转移,需要支持修改操作,而线段树等数据结构都是 \(O(nk^2 \log n)\) 的,无法接受。
这个修改操作实际上只会依次在末尾加数,而不会改变已经添加过的值。
这可以使用 ST 表维护:把 ST 表倒过来,令 \(st_{i,j}\) 表示 \([j-2^i+1,j]\) 的最大值。这样即可方便地在末尾以 \(\log n\) 的复杂度更新 ST 表。

时间复杂度 \(O(nk^2)\)

Code
const int N=2e5+5,M=15,inf=1e9;
int T,n,m,k;
vector<int> t1[N],t2[N];
multiset<int> s;
vector<int> a[N];
int f[M][N];
struct ST
{
    int st[20][N];
    void ins(int x,int i)
    {
        st[0][i]=f[x][i];
        for(int j=1;i-(1<<j-1)>=0;j++) st[j][i]=max(st[j-1][i],st[j-1][i-(1<<j-1)]);
    }
    il int ask(int l,int r)
    {
        if(l>r) return -inf;
        int len=__lg(r-l+1);
        return max(st[len][r],st[len][l+(1<<len)-1]);
    }
}st[M];
int main()
{
    T=read();
    while(T--)
    {
        n=read(),m=read(),k=read();
        for(int i=1;i<=n;i++) t1[i].clear(),t2[i].clear(),a[i].clear();
        s.clear();
        for(int i=1;i<=m;i++)
        {
            int l=read(),r=read();
            t1[l].push_back(l),t2[r+1].push_back(l);
        }
        for(int i=1;i<=n;i++)
        {
            for(auto x:t1[i]) s.insert(x);
            for(auto x:t2[i]) s.erase(s.find(x));
            if(s.size()>k) a[i].push_back(-1);
            else {for(auto x:s) a[i].push_back(x); a[i].push_back(i);}
        }
        for(int i=1;i<=n;i++) f[0][i]=f[0][i-1]+(a[i].size()==1&&a[i][0]!=-1);
        st[0].ins(0,0);
        for(int i=1;i<=n;i++) 
        {
            if(!a[i].empty()&&a[i][0]==-1) f[0][i]=0;
            st[0].ins(0,i);
        }
        for(int j=1;j<=k;j++)
        {
            for(int i=1;i<=n;i++)
            {
                int lst=0,now=a[i].size()-1; f[j][i]=0;
                if(a[i].size()==1&&a[i][0]==-1) {st[j].ins(j,i);continue;}
                for(auto x:a[i])
                {
                    if(j-now<0) break;
                    f[j][i]=max(f[j][i],1+st[j-now].ask(lst,x-1));
                    now--,lst=x;
                }
                st[j].ins(j,i);
            }
        }
        printf("%d\n",st[k].ask(0,n));
    }
    return 0;
}