Codeforces Round #496 (Div. 3) C D E1 E2 F

Codeforces Round #496 (Div. 3)

Problem A

题意:

思路:



Problem B

题意:

思路:



Problem C

题意: 给出n个数,询问最少删除几个数使得剩下的每一个数总能找到对应的另一个数使得两个数的和是 2 x 2^x 2x(x为非负整数)

思路: 枚举每个数形成 2 0 2^0 20~小于2*1e9的最大的 2 x 2^x 2x所需的另一个数,只要存在当前的数就是OK的,当然要排除自身是 2 x 2^x 2x然后找到的另一半也是 2 x 2^x 2x并且是自己的情况

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=(a);i<=(b);i++)
#define for0(i,a,b) for (int i=(a);i<(b);i++)
#define rof1(i,a,b) for (int i=(a);i>=(b);i--)
#define rof0(i,a,b) for (int i=(a);i>(b);i--)
#define fi first
#define se second
#define pb push_back
#define duozu int __T;scanf("%d",&__T);for1(ica,1,__T)
void _r(const int&x){scanf("%d",&x);}
void _r(const ll&x){scanf("%I64d",&x);}
void _r(const char*x){scanf("%s",x);}
void R(){}
template<class T,class... U>void R(const T&head,const U&...tail){_r(head);R(tail...);}
void _w(const char x){putchar(x);}
void _w(const char*x){printf("%s",x);}
void _w(const int x){printf("%d",x);}
void _w(const ll x){printf("%I64d",x);}
void _w(const double x){printf("%.6f",x);}
void W(){}
template<class T,class...U>void W(const T&head,const U&...tail){_w(head);putchar(sizeof...(tail)?' ':'\n');W(tail...);}
const ll mod = 1e9+7;
ll fp1(ll a,ll b){ll ans=1;while (b){if(b&1)ans*=a;b>>=1;a*=a;}return ans;}
ll fp2(ll a,ll b){ll ans=1;while (b){if(b&1)ans=ans*a%mod;b>>=1;a=a*a%mod;}return ans;}
ll Read(){
    ll res=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-')flag = -1;ch = getchar();}
    while (ch>='0' && ch<='9'){res = res*10+ch-'0';ch = getchar();}
    return res*flag;
}

const int N = 12e4+5;

int a[N];
ll bina[50],idx;
unordered_map<int,int>mp;

void prework(){
    idx = 0;
    while ((1<<idx)<=1e9) {bina[idx] = 1<<idx;idx++;}
    bina[idx] = 1<<idx;
    idx++;
}

int main()
{
    int n;
    prework();
    //for0(i,0,idx) W(bina[i]);
    while (~scanf("%d",&n)){
        int ans = 0;
        mp.clear();

        for0(i,0,n){
            R(a[i]);
            mp.count(a[i])?mp[a[i]]++:mp[a[i]]=1;
        }

        for0(i,0,n){
            int j = 0;
            for (;j<idx;j++){
                int need = bina[j] - a[i];
                if (mp.count(need)){
                    if (need==a[i] && mp[need]>1) break;
                    else if (need != a[i]) break;
                }
            }
            if (j==idx) ans++;
        }
        
        W(ans);
    }
    return 0;
}



Problem D

题意: 给出一个只含0~9的串,可以在不产生前导0的情况下任意切割,询问最多产生多少个能被3整除的数

思路: 首先所有0369都单独切除,因为0369本身能被三整除,其他数与其和这些0相连组成一个数不如省下来说不定还能凑出更多的数,对于剩余的数分两类:s[i]%3=1,s[i]%3=2。因此只剩两种数了,1和2。于是现在我们得到了很多段只剩12的串。
怎么处理每一段使得每一段被三整除的数尽可能多呢。很显然我们需要从前往后找最短的,只要能组成就马上用掉,为什么这样最优呢,如果当前能组却不组肯定是为了之后能组的更多,那么当前能组的一定要和后面的用掉,结果不仅数量相同,剩下提供的后缀比原来还短了!
那么怎么样能用就用掉呢?首先至少需要两个,假设下标从0开始(后面用下标指代那个数),那么我们从1判断,我们判断0和1是否相反,相反就直接用掉直接组成3了,然后往后跳2位,去判断3和2是否相反。而如果0和1相同,那么我们再看2,如果相同,那么这三位用掉,然后往后跳3位因为下一位也用掉了,如果2和1不同,那也不用慌,因为这样21就能组成3的倍数了,显然这是前三位最快组成3的倍数的方法。

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=(a);i<=(b);i++)
#define for0(i,a,b) for (int i=(a);i<(b);i++)
#define rof1(i,a,b) for (int i=(a);i>=(b);i--)
#define rof0(i,a,b) for (int i=(a);i>(b);i--)
#define fi first
#define se second
#define pb push_back
#define duozu int __T;scanf("%d",&__T);for1(ica,1,__T)
void _r(const int&x){scanf("%d",&x);}
void _r(const ll&x){scanf("%I64d",&x);}
void _r(const char*x){scanf("%s",x);}
void R(){}
template<class T,class... U>void R(const T&head,const U&...tail){_r(head);R(tail...);}
void _w(const char x){putchar(x);}
void _w(const char*x){printf("%s",x);}
void _w(const int x){printf("%d",x);}
void _w(const ll x){printf("%I64d",x);}
void _w(const double x){printf("%.6f",x);}
void W(){}
template<class T,class...U>void W(const T&head,const U&...tail){_w(head);putchar(sizeof...(tail)?' ':'\n');W(tail...);}
const ll mod = 1e9+7;
ll fp1(ll a,ll b){ll ans=1;while (b){if(b&1)ans*=a;b>>=1;a*=a;}return ans;}
ll fp2(ll a,ll b){ll ans=1;while (b){if(b&1)ans=ans*a%mod;b>>=1;a=a*a%mod;}return ans;}
ll Read(){
    ll res=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-')flag = -1;ch = getchar();}
    while (ch>='0' && ch<='9'){res = res*10+ch-'0';ch = getchar();}
    return res*flag;
}

const int N = 2e5+5;

char s[N];
vector<int>a[N];

int main()
{
    while (~scanf("%s",s)){
        int idx = 0;
        int ans = 0;
        a[0].clear();
        for (int i=0;s[i];i++){
            int num = (s[i]-'0')%3;
            if (!num) {ans++;a[++idx].clear();}
            else a[idx].pb(num);
        }
        for1(i,0,idx){
            for0(j,1,a[i].size()){
                if (j+1<a[i].size() && a[i][j-1]==a[i][j] && a[i][j]==a[i][j+1]){
                    ans++;
                    j+=2;
                }
                else if (a[i][j]!=a[i][j-1]){
                    ans++;
                    j++;
                } 
            }
        }
        W(ans);
    }
    return 0;
}



Problem E1

题意: 给出n个数,是1~n这n个数的某种排列,询问区间排序后中位数是m的区间的个数

思路: 遍历一遍数组,找到m的位置,并把其他数做处理——大于m/小于m变成1/-1。m左右分为两段。我们相当于从m的位置左右延伸确定左右界,使得区间内除了m其他数的和为0或者1即可使m为中位数。我们预处理一下m右边所有可能的前缀和以及对应次数。左边的话从右到左遍历,求和,并在对应右边找使得和为0/1的数是否存在,复杂度O(N)

需要注意的是m本身也是答案,以及区间只需一边的情况。

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=(a);i<=(b);i++)
#define for0(i,a,b) for (int i=(a);i<(b);i++)
#define rof1(i,a,b) for (int i=(a);i>=(b);i--)
#define rof0(i,a,b) for (int i=(a);i>(b);i--)
#define fi first
#define se second
#define pb push_back
#define duozu int __T;scanf("%d",&__T);for1(ica,1,__T)
void _r(const int&x){scanf("%d",&x);}
void _r(const ll&x){scanf("%I64d",&x);}
void _r(const char*x){scanf("%s",x);}
void R(){}
template<class T,class... U>void R(const T&head,const U&...tail){_r(head);R(tail...);}
void _w(const char x){putchar(x);}
void _w(const char*x){printf("%s",x);}
void _w(const int x){printf("%d",x);}
void _w(const ll x){printf("%I64d",x);}
void _w(const double x){printf("%.6f",x);}
void W(){}
template<class T,class...U>void W(const T&head,const U&...tail){_w(head);putchar(sizeof...(tail)?' ':'\n');W(tail...);}
const ll mod = 1e9+7;
ll fp1(ll a,ll b){ll ans=1;while (b){if(b&1)ans*=a;b>>=1;a*=a;}return ans;}
ll fp2(ll a,ll b){ll ans=1;while (b){if(b&1)ans=ans*a%mod;b>>=1;a=a*a%mod;}return ans;}
ll Read(){
    ll res=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-')flag = -1;ch = getchar();}
    while (ch>='0' && ch<='9'){res = res*10+ch-'0';ch = getchar();}
    return res*flag;
}

const int N = 2e5+5;

map<int,int>mp;
vector<int>a,b;

int main()
{
    int n,m;
    while (~scanf("%d %d",&n,&m)){
        mp.clear();a.clear();b.clear();
        bool flag = false;

        for1(i,1,n){
            int x;
            R(x);
            if (x==m) flag = true;
            else flag?b.pb(x>m?1:-1) : a.pb(x>m?1:-1);
        }
        for (int i=a.size()-2;i>=0;i--) a[i] += a[i+1];
        for (int i=1;i<b.size();i++) b[i] += b[i-1];
        for (int i=0;i<b.size();i++) mp.count(b[i])==0?mp[b[i]]=1:mp[b[i]]++;
        ll ans = 1;
        if (mp.count(1)) ans += mp[1];
        if (mp.count(0)) ans += mp[0];
        for (int i=0;i<a.size();i++){
            if (a[i]==0||a[i]==1) ans++;
            if (mp.count(0-a[i])) ans += mp[0-a[i]];
            if (mp.count(1-a[i])) ans += mp[1-a[i]];
        }
        W(ans);
    }
    return 0;
}



Problem E2

题意: 长度为n的数列,询问区间中位数是m的区间的个数。m可能有多个

思路: 把求大于等于m的个数比小于m多的区间段数,定义为GC(m)。于是我们可以把问题转化成求GC(m)-GC(m+1),把大于等于m的数转化为1,其余-1,即相当于求区间和大于0的区间个数。两个问题等价,只需讲其中一个,具体做法是维护前缀和,记录每种前缀出现次数,每个前缀我们都求去掉这个前缀的哪些前缀可以使得结果>0,即求前缀中比当前前缀和小的,相当于动态查找 < sum的数的个数,C++set不支持,如果想实现只能自己写平衡树。 但是本题多了一个特性每次前缀和要么+1,要么-1,假设我们维护小于当前sum的前缀数量,由于我们统计了每个sum的数量,每次只需要O(1)增加或者减少某一个前缀的数量。显然如果每次前缀和的变化跨度很大这个做法就不适用了。每次修改都是O(N)。

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=(a);i<=(b);i++)
#define for0(i,a,b) for (int i=(a);i<(b);i++)
#define rof1(i,a,b) for (int i=(a);i>=(b);i--)
#define rof0(i,a,b) for (int i=(a);i>(b);i--)
#define fi first
#define se second
#define pb push_back
#define duozu int __T;scanf("%d",&__T);for1(ica,1,__T)
void _r(const int&x){scanf("%d",&x);}
void _r(const ll&x){scanf("%I64d",&x);}
void _r(const char*x){scanf("%s",x);}
void R(){}
template<class T,class... U>void R(const T&head,const U&...tail){_r(head);R(tail...);}
void _w(const char x){putchar(x);}
void _w(const char*x){printf("%s",x);}
void _w(const int x){printf("%d",x);}
void _w(const ll x){printf("%I64d",x);}
void _w(const double x){printf("%.6f",x);}
void W(){}
template<class T,class...U>void W(const T&head,const U&...tail){_w(head);putchar(sizeof...(tail)?' ':'\n');W(tail...);}
const ll mod = 1e9+7;
ll fp1(ll a,ll b){ll ans=1;while (b){if(b&1)ans*=a;b>>=1;a*=a;}return ans;}
ll fp2(ll a,ll b){ll ans=1;while (b){if(b&1)ans=ans*a%mod;b>>=1;a=a*a%mod;}return ans;}
ll Read(){
    ll res=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-')flag = -1;ch = getchar();}
    while (ch>='0' && ch<='9'){res = res*10+ch-'0';ch = getchar();}
    return res*flag;
}

const int N = 2e5+5;

unordered_map<int,int>mp;
int a[N],n;

ll GreatCount(int m){
    ll res = 0,sum = 0,add = 0;
    mp.clear();
    mp[0] = 1;
    for0(i,0,n){
        if (a[i]>=m){
            if (mp.count(sum)==1) add += mp[sum];
            sum++;
        }
        else {
            sum--;
            if (mp.count(sum)==1) add -= mp[sum];
        }
        mp.count(sum)==1?mp[sum]++:mp[sum] = 1;
        res += add;
    }
    return res;
}

int main()
{
    int m;
    while (~scanf("%d %d",&n,&m)){
        for0(i,0,n) R(a[i]);
        W(GreatCount(m)-GreatCount(m+1));        
    }
    return 0;
}

Problem F

题意: 给出一张n个点m条边的图,保证n个点连通。让你选择其中n-1条边将n个点连接并使得所有点到1号点的总路程和(每一条边长度都记为1)最近。你需要找到k种选择方案,如果总方法数少于k种,就找到所有方法即可。

思路: 先试想怎样使得总路程和最近,就是每一个点尽早与1相连,我们可以从1进行BFS,每个点被遍历到的层数就是距离1最短的距离,记做d[i]。如果我们要找总路程最小的不同方法,那么每个点必须在那一层被遍历到。所以不同方法的本质就是在最初BFS形成的一层层节点中,每一层的节点不变,但是改变‘这个节点是上一层谁的儿子’。因此我们维护一个fa[i][]表示i节点可以选择的父亲对应那条边的编号的集合,这个根据d[],遍历一遍所有边即可。

  然后就相当于对fa在排列组合出k种方法了。怎么样不重复的枚举完呢?感觉官方题解的做法挺牛批的,另开一个数组( 下方代码中记做nt[i] )记录对应fa[]当前枚举到第几个父亲了。
然后下一个节点更换父亲当且仅当当前所有位都恰好枚举完了最后一个,整一个过程相当于左位取满,向右边进1的过程。
举个例子,我们用1321模拟一下,就是1111,1211,1311,1121,1221,1321

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=(a);i<=(b);i++)
#define for0(i,a,b) for (int i=(a);i<(b);i++)
#define rof1(i,a,b) for (int i=(a);i>=(b);i--)
#define rof0(i,a,b) for (int i=(a);i>(b);i--)
#define fi first
#define se second
#define pb push_back
#define duozu int __T;scanf("%d",&__T);for1(ica,1,__T)
void _r(const int&x){scanf("%d",&x);}
void _r(const ll&x){scanf("%I64d",&x);}
void _r(const char*x){scanf("%s",x);}
void R(){}
template<class T,class... U>void R(const T&head,const U&...tail){_r(head);R(tail...);}
void _w(const char x){putchar(x);}
void _w(const char*x){printf("%s",x);}
void _w(const int x){printf("%d",x);}
void _w(const ll x){printf("%I64d",x);}
void _w(const double x){printf("%.6f",x);}
void W(){}
template<class T,class...U>void W(const T&head,const U&...tail){_w(head);putchar(sizeof...(tail)?' ':'\n');W(tail...);}
const ll mod = 1e9+7;
ll fp1(ll a,ll b){ll ans=1;while (b){if(b&1)ans*=a;b>>=1;a*=a;}return ans;}
ll fp2(ll a,ll b){ll ans=1;while (b){if(b&1)ans=ans*a%mod;b>>=1;a=a*a%mod;}return ans;}
ll Read(){
    ll res=0,flag=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if (ch=='-')flag = -1;ch = getchar();}
    while (ch>='0' && ch<='9'){res = res*10+ch-'0';ch = getchar();}
    return res*flag;
}

const int N = 2e5+5;

int a[N],b[N];
vector<int>fa[N],g[N];
vector<string>res;
int nt[N],d[N];
queue<int>que;

int main()
{
    int n,m,k;
    R(n,m,k);
    for1(i,1,m){
        R(a[i],b[i]);
        a[i]--,b[i]--;
        g[a[i]].pb(b[i]);
        g[b[i]].pb(a[i]);
    }
    que.push(0);
    d[0] = 1;
    while (!que.empty()){
        int u = que.front();
        que.pop();
        for (auto v:g[u]){
            if (!d[v]){
                d[v] = d[u]+1;
                que.push(v);
            }
        }
    }

    for1(i,1,m){
        int u = a[i],v = b[i];
        if (d[u]+1==d[v]) fa[v].pb(i);
        if (d[v]+1==d[u]) fa[u].pb(i);
    }

    for1(i,1,k){
        string s(m,'0');
        for1(i,1,n-1){
            s[fa[i][nt[i]]-1] = '1';//字符串下标从0开始而线段从1开始所以-1
        }
        res.pb(s);
        bool ok = false;
        for1(i,1,n-1){
            if (nt[i]+1<fa[i].size()){
                nt[i]++;
                ok = true;
                break;
            }
            else nt[i] = 0;
        }
        if (!ok) break;
    }

    cout << res.size() << endl;
    for (auto s:res) cout << s << endl;

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值