Codeforces Round 906 (Div. 2)(D推公式 E1分类讨论区间 E2 dp+线段树)

A - Doremy's Paint 3

推公式得

b1=b3=b5=b7....

b2=b4=b6=b8...

所以如果只有一个数或者两个数且数量差小于等于1即可

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
void solve()
{
    cin>>n;
    map mp;
    for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
    sort(a+1,a+1+n);
    if(mp.size()<=2)
    {
        if(mp.size()==1)
        {cout<<"Yes\n";return ;}
        else
        {
            auto x=mp[a[1]];
            auto y=mp[a[n]];
            if(abs(x-y)<=1){
                cout<<"Yes\n";
            }
            else cout<<"No\n";
        }
    }
    else cout<<"No\n";
}

signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    int t=1;
    cin>>t;
    while(t--) solve();
}

B - Qingshan Loves Strings

老规矩操作题先思考操作的性质

如果T串本身不合法那么就NO了,且我们插入肯定是在相同的时候插入

比如 相邻的0 或相邻的1,这时候要求T串首尾要不同且能接的上

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
bool check(string s){
    for(int i=1;i>n>>m;
    string s,t;
    cin>>s>>t;
    if(check(s))
    {
        cout<<"YES\n";return ;    
    }
    else{
        if(!check(t)||t[0]!=t.back()){
            cout<<"NO\n";return ;
        }
    }
    for(int i=1;i>t;
    while(t--) solve();
}

C:C - Qingshan Loves Strings 2

看范围100 所以可以n^2去暴力

考虑插入哪个位置

如果 0XXXX0,因为只能插入 01 那么这个只能插入到翻转位置后的那个位置,否则就是无效插入

如果是1XXXX1,因为只能插入01 只能插入到第一个字母的前面

所以可得 如果当前是0 那么插入当前位置的翻转后的位置,如果当前是1,那么插入到当前位置的前面的位置

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
int check(string s){
    int n=s.size()-1;
    for(int i=1;i<=(n+1)/2;i++){
        if(s[i]==s[n-i+1]){
            return i;
        }
    }
    return 0;
}
void solve()
{
    cin>>n;
    string s;
    cin>>s;
    vector res;
    s="?"+s;
    for(int j=1;j<=300;j++)
    {
        int x=check(s);
        if(!x)
        {
            cout<>t;
    while(t--) solve();
}

D - Doremy's Connecting Plan

这是个推公式题,肯定是用已经联通的块连向其他点

设只有两个数

ai+ aj >=i*j*c

然后发现好像不太能合并,然后可以想到1如果i=1,那么就单纯的变成

0>=j*c-aj,然后后面一直用1点去联通即可

证明

ai + aj>=i*j*c

a1+ai

a1+aj

假设只能连 i j的点,不能先连 1点,

用反证法得

a1+a1<(i+j-i*j)*c

因为i和j不能为1

所以这个(i+j-i*j)肯定是负数,不满足等式,所以证明如果能连i和j点,必然可以先连1点

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
void solve()
{
    int c;
    cin>>n>>c;
    priority_queue,greater> q;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(i!=1) q.emplace(i*c-a[i],i);
    }
  
    auto now=a[1];
    while(q.size())
    {
        auto t=q.top();
        if(now>=t.first){
            now+=a[t.second];
            q.pop();
        }
        else break;
    }
    if(q.size()) cout<<"No\n";
    else cout<<"Yes\n";
}

signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    int t=1;
    cin>>t;
    while(t--) solve();
}

E1 - Doremy's Drying Plan (Easy Version)

因为k=2,所以思考选哪两个区间

1.这两个区间相交

2.这两个区间不相交

因为只能去掉两个区间,所以如果有些点被大于两个区间得点覆盖直接去掉就行了,

对于第二个条件两个区间不相交,说明如果去掉某个区间,那么这个点只能被一个区间覆盖

所以我们预处理

s1:只被一个区间覆盖的点的前缀和

s2:只被两个区间覆盖的点的前缀和

然后第二个条件直接枚举每个区间的贡献的最大值和次大值相加即可

然后可能有人问万一最大值和次大值区间相交呢,这说明这个相交的点要被覆盖两次,

我们求这个用的是s1数组,所以不会重复计算贡献

即任意取两个区间

如果相交的部分至少被覆盖两次他们不会在s1的数组

不相交的部分相加即为第二个条件的答案

对于第一个条件

我们直接暴力枚举每个点,以当前端点为相交的部分即可

然后用个堆枚举即可

贡献就是 两个区间相交部分的s2区间和maxr到minl的s1部分

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
void solve()
{
    cin>>n>>m>>k;
    vector s1(n+10),s2(n+10),d(n+10);
    vector> a;
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        a.push_back({x,y});
        d[x]++,d[y+1]--;
    }
        int res=0;
    for(int i=1;i<=n;i++){
        d[i]+=d[i-1];
        if(d[i]==0) res++;
        if(d[i]==1) s1[i]++;
        if(d[i]==2) s2[i]++;
        s1[i]+=s1[i-1];s2[i]+=s2[i-1];
    }
    int cnt1=0,cnt2=0;

    for(int i=0;icnt1){
            cnt2=cnt1,cnt1=cnt;
        }
        else if(cnt>cnt2) cnt2=cnt;
    }
    int mx=cnt1+cnt2;
    
    priority_queue q;
    int idx=0;
    sort(a.begin(),a.end());
    for(int i=1;i<=n;i++)
    {
        while(idx=2)
        {
            auto t1=q.top();q.pop();
            auto t2=q.top();
            vector c={t1.first,t1.second,t2.first,t2.second};
            sort(c.begin(),c.end());
            int cnt=s1[c[3]]-s1[c[0]-1]+(s2[c[2]]-s2[c[1]-1]);
            mx=max(mx,cnt);
            q.push(t1);
        }
    }
    cout<>t;
    while(t--) solve();
}

E2 - Doremy's Drying Plan (Hard Version)

考虑dp

状态:第i个点没被区间覆盖,且已经使用了j次机会的最大值

!!!!重点使得当前i点没被区间覆盖

Codeforces Round 906 (Div. 2)(D推公式 E1分类讨论区间 E2 dp+线段树)_第1张图片

举例说明

当前点5,使用了5次机会可以由

第4个点使用了5次机会获得(为啥机会没-1呢

因为f[4]代表着没被区间覆盖,说明f[4]已经把前面能覆盖到4的点的区间已经删除了)

第3个点使用4次机会获得

这里要把4到5的区间删去

以此类推

所以我们要找这个区间的最大值了

因为可能我后面再加个6的点,但我不增加区间,他可以由f[4][5]和f[5][5]最大值获得

所以涉及区间查询最大值,但是k很小,直接暴力每个k开个线段树即可

#include
using namespace std;
const int N = 2e5+10,mod=1000003;
#define int long long
typedef long long LL;
typedef pair PII;
const long long inf=1e17;
int n,m,k;
vector g[N];
int a[N];
struct node{
    int l,r,mx;
}tr[11][N*4];
void pushup(int p,int u){
    tr[p][u].mx=max(tr[p][u<<1].mx,tr[p][u<<1|1].mx);
}
void build(int p,int u,int l,int r)
{
    tr[p][u]={l,r,0};
    if(l==r) return ;
    int mid=l+r>>1;
    build(p,u<<1,l,mid);build(p,u<<1|1,mid+1,r);
}

void modify(int p,int u,int x,int v){
    if(tr[p][u].l==tr[p][u].r&&tr[p][u].l==x){
        tr[p][u].mx=max(tr[p][u].mx,v);
        return ;
    }
    else{
        int mid = tr[p][u].l + tr[p][u].r >> 1;
        if(x <= mid) modify(p, u << 1, x,v);
        else modify(p, u << 1 | 1, x,v);
        pushup(p, u);
    }
}
int query(int p, int u, int l, int r) {
    if(!l && !r) return 0;
    if(l <= tr[p][u].l && r >= tr[p][u].r) return tr[p][u].mx;
    int mid = tr[p][u].l + tr[p][u].r >> 1;
    int res = 0;
    if(l <= mid) res = max(res, query(p, u << 1, l ,r));
    if(r > mid) res = max(res, query(p, u << 1 | 1, l, r));
    return res;
}

void solve()
{
    cin>>n>>m>>k;
    vector> seg;
    for(int i=0;i<=k;i++) build(i,1,1,n);
    vector> f(n+10,vector(k+10));
    for(int i=0;i>x>>y;
        seg.push_back({x,y});
    }
    sort(seg.begin(),seg.end());
    multiset st;
    int res=0;
    for(int i=1,idx=0;i<=n;i++){
        while(idx S{0};
            S.push_back(i);
            for(auto [r, l] : st) S.push_back(l);
            sort(S.begin(), S.end());
            for(int j = st.size(); j <= k; j ++ ) {
                for(int z = S.size() - 2, w = 0; w <= j && z >= 0; w ++, z -- ) {
                    int l = S[z], r = S[z + 1] - 1;
                    if(l > r) continue;
                    f[i][j] = max(f[i][j], query(j - w, 1, l ,r) + 1);
                    modify(j, 1, i, f[i][j]);
                }
            }
            res = max(res, f[i][k]);
        }
        PII tmp;
        while(st.size() && (tmp = *(st.begin())).first == i)
        st.extract(tmp);
    }
    cout<>t;
    while(t--) solve();
}

你可能感兴趣的:(codeforce,算法)