把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

20180719 练习赛 [CF]496DIV3

A - Tanya and Stairways CodeForces - 1005A
B - Delete from the Left CodeForces - 1005B
C - Summarize to the Power of Two
D - Polycarp and Div 3 CodeForces - 1005D
E - Median on Segments (Permutations Edition) CodeForces - 1005E1
F - Berland and the Shortest Paths CodeForces - 1005F
G - Median on Segments (General Case Edition) CodeForces - 1005E2

A

有一个人上台阶,台阶是递增的
求有多少个台阶以及每个台阶的最高点
【题意其实可以样例分析法】

水题 模拟

#include<cstdio>
#include<map>
using namespace std;
#define LL long long
#define MAXN 1006
int N,ans;
int a[MAXN];
int main()
{
    scanf("%d",&N);
    int x,y=1;
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&x);
        if(x==1&&i!=1) a[++ans]=y;
        y=x;
    }
    a[++ans]=x;
    printf("%d\n",ans);
    for(int i=1;i<ans;i++)
        printf("%d ",a[i]);
    printf("%d\n",a[ans]);
}

B

两个字符串,从左往右删,直到两个串相等或其中一个为空,求最少删多少

由于是从左往右删 所以显然是右边留下的字符串相等 我们对于两个字符串从右到左扫描 如果不一样则前面的全部都要删去

#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define LL long long
#define MAXN 200005
char s[MAXN],t[MAXN];
int main()
{
    scanf("%s\n%s",s+1,t+1);
    int ls=strlen(s+1),lt=strlen(t+1);
    int ns=ls,nt=lt;
    while(1)
    {
        if(ns<1){ printf("%d\n",nt); return 0;}
        if(nt<1){ printf("%d\n",ns); return 0;}
        if(s[ns]!=t[nt])
        {
            printf("%d\n",nt+ns);
            return 0;
        }
        ns--,nt--;
    }
    return 0;
}

C

给你n个数,问最少去掉几个数后,每个数都能找到另一个数与它相加之和为2的幂

显然,原数列中,如果一个数不满足如题的条件,则要将它删除(因为这题只能删,不能加,而一个数只要不满足条件就“无可救药”)
所以就暴力啦
但是考虑到n比较大,而范围内2的幂的个数比较小,所以可以枚举2的幂,然后减去这个值,再在原数列中查找是否有这个数。

但是细节蛮多的,注意重复的情况,如果减下来是它本身而原数列里又只有一个这个数会WA。但如果特判的话又会有多个相同的数的情况。_ (:з」∠)_

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define LL long long
#define MAXN 120010
const LL p[31]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,
524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824};
LL a[MAXN];
bool b[MAXN];
int N,ans;
int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++)
        scanf("%lld",&a[i]);
    sort(a,a+N);//  _bound must be used with sorted sequence
    for(int i=0;i<N;i++)
    {
        bool f=0;
        for(int j=0;j<=30;j++)
        {
            LL temp=p[j]-a[i];
            if(temp<0) continue;
            if(temp>a[N-1]) break;
            int pos=lower_bound(a,a+N,temp)-a;
            if(pos==i&&a[pos+1]!=a[pos]&&a[pos-1]!=a[pos]) continue;
            if(a[pos]==temp) 
            {
                f=1;
                break;
            }
        }
        if(!f) ans++; 
    }
    printf("%d\n",ans);
    return 0;
}
//detail is important

D

给出一个很大很大的数,可以无限次分割,求最后所有分割而成的数中能被3整除的数量最多。

能被3整除的数各个数位之和都是3的倍数 所以这个问题转化为了:最多有多少段不重叠的区间和是3的倍数??? 然后 线段树???
(然而考试中放弃了这个复杂的想法)

这道题的难点在于要不要考虑“放弃”当前这个数。(就是说让不让这个数对答案有贡献)
例如:311539 我们应该“放弃”第1个1,否则就不能得到最优解。
可是枚举妥妥地超时。

正解的话,数学推导一下:
这里有个性质,就是每三位数中至少存在1种分割能被3整除
一位数如果能被3整除,就直接ans++
前两位数如果能被3整除,也直接ans++
如果上面两种情况都不满足,那么前两位数的余数一定为(1,1)或(2,2)两种情况。对于第三位数,除以3的余数为0,1,2.
如果为0,直接就可以。
如果为1,对于余数的第一种情况,3个数合在一起就能被3整除;对于余数的第二种情况,这个数和第二个数合在一起就可以。
如果为2,对于余数的第一种情况,这个数和第二个数合在一起就可以被3整除;3个数合在一起就能被3整除。

综上,那么以下3种情况ans累计:

  1. 这个数能被3整除
  2. 这个数加上一个数能被3整除(这种情况可以记录一下余数)
  3. 这个数的前面2个数都没有出现过以上2种情况。

然后就可以O(n)统计答案了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define LL long long
#define MAXN 200006
char s[MAXN];
int N,ans;
int main()
{
    scanf("%s",s+1);
    N=strlen(s+1);
    int nr=0,n=0;
    for(int i=1;i<=N;i++)
    {
        int r=(s[i]-'0')%3;
        nr+=r;
        n++;
        if(nr==3||r==0||n==3)
        {
            ans++;
            nr=0,n=0;
        }
    }
    printf("%d\n",ans);
    return 0;
}

E

给定一个长度为n的数列{p1,p2,…,pn}由{1,2,…,0}组成。(其实就是排列)
求满足中位数为m的闭区间[l,r]的个数。
(当区间内元素为偶,中位数取中间靠左的那个数)


排列不存在重复元素 虽然顺序被打乱
但是答案区间满足:比m大的数-比m小的数=0或1(偶数长度取左边)
即:比m大的数=比m小的数比m大的数=比m小的数+1
用pos表示m的位置 c[i] 表示pos~N区间中 满足比m大的数-比m小的数=i (即pos左边还需要i或i+1个小于m的数[这里指“有效的小于”,因为pos左边也存在大于m的数]) 的区间个数

注意i可能为负数 可以用map或者数组平移

实现的话,可以转化一下思想,代码会好想一点。
说得浅显一点 就是大于m的数记为1 小于m的数记为-1 m记为0 求区间和为0或1
(这么一想,似乎可以用前缀和,然而我并没有,我是记录了一下,用c[i]表示pos右边值为i的区间个数)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define LL long long
#define MAXN 200005
int p[MAXN],c[2*MAXN];
int N,M,pos,cnt;
LL ans;//注意ans开long long (虽然我肯定会忘记 然后爆得可开心啦)
int main()
{
    scanf("%d %d",&N,&M);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&p[i]);
        if(p[i]==M) pos=i;
    }
    for(int i=pos;i<=N;i++)
    {
        if(p[i]>M) cnt++;
        if(p[i]<M) cnt--;
        c[MAXN+cnt]++;
    }
    cnt=0;
    for(int i=pos;i>=1;i--)//注意必须倒序 区间是连续的
    {
        if(p[i]<M) cnt++;
        if(p[i]>M) cnt--;
        ans+=c[MAXN+cnt];
        ans+=c[MAXN+cnt+1];
    }
    printf("%lld\n",ans);
    return 0;
}

然后我又用前缀和的思路写了一下,然后在23个点上TLE_ (:з」∠)_ 上面的那个实现是O(n)的,前缀和是O(n^2)的,虽然可以优化一下区间的起点最大只能到pos那儿

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
#define LL long long
#define MAXN 200005
int p[MAXN],c[MAXN];
int N,M,pos,cnt;
LL ans;
int main()
{
    scanf("%d %d",&N,&M);
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&p[i]);
        if(p[i]<M) c[i]=-1;
        else if(p[i]>M) c[i]=1;
        else c[i]=0,pos=i;
    }
    for(int i=1;i<=N;i++)
        c[i]=c[i-1]+c[i];
    for(int i=1;i<=pos;i++)
        for(int j=i;j<=N;j++)
            if((c[j]-c[i-1]==0||c[j]-c[i-1]==1)&&(i<=pos&&pos<=j))
                ans++;
    printf("%lld\n",ans);
    return 0;
}

F

一张图有n个顶点,m条边,现在要求选择n-1条边,输出<=k个方案.
每个方案满足:顶点1到任何1个顶点的距离 总和最小

其实就是比较裸的一个最短路树的问题
参考了一下这位大佬的博客
Berland and the Shortest Paths

#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define INF 1000000000
#define LL long long
int n,m,k,cnt;
int dis[MAXN],chose[MAXN];
vector<pair<int,int> >G[MAXN];
vector<pair<int,int> >medge[MAXN];

void bfs(int s)
{
    fill(dis,dis+MAXN,INF);
    dis[s]=0;
    queue<int> que;
    que.push(s);
    while(!que.empty())
    {
        int u=que.front(); que.pop();
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i].first,id=G[u][i].second,stp=dis[u]+1;
            if(stp>dis[v]) continue;
            medge[v].push_back(make_pair(u,id));
            if(stp!=dis[v])
                dis[v]=stp,que.push(v);
        }
    }
}

void dfs(int u)//找方案
{
    if(u==n+1)
    {
        cnt++;
        for(int i=1;i<=m;i++)
            printf("%d",chose[i]);
        printf("\n");
        return ;
    }
    for(int i=0;i<medge[u].size();i++)
    {
        chose[medge[u][i].second]=1;
        dfs(u+1);
        chose[medge[u][i].second]=0;
        if(cnt==k)
            return ;
    }
}
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(make_pair(v,i));
        G[v].push_back(make_pair(u,i));
    }
    bfs(1);
    LL ans=1;
    for(int i=2;i<=n;i++)
    {
        ans*=medge[i].size();
        if(ans>k)
            break;
    }
    printf("%lld\n",min(k*1LL,ans));
    dfs(2);
    return 0;
}

G

题意与E题一样
但是这道题有多个M
照着原来的方法多搞几下吧,超不超时先不说,关键是要考虑区间可能会有重叠部分┓( ´∀` )┏
这题看了题解,思路挺清晰的,但我不会莫队_ (:з」∠)_
所以后面那个优化没怎么懂,便予诸位看官一览(后有我本人的代码):


以下来源于题解:

首先我们计算出solve(m):中位数大于等于m的方案数区间数,那么最后答案就是solve(m) - solve(m+1)

那么怎么计算sovle(m)呢?

对于一个区间[l,r],如果它的中位数大于等于m,那么这个区间中 (大于等于m的数的个数) > (小于m的数的个数)

如果记a[i]大于等于m为+1,小于m 为 -1,即 sum(l, r) > 0

————其实这里以上就是之前的思路————————

我们枚举右端点 i ,并且同时计算sum(1, i) ,那么对于这个右端点,我们只要找到之前的 sum 中 < sum(1, i)的个数(左端点的个数),这个可以用树状数组维护

但是我们有一个O(n)的方法求,用了类似莫队的方法,记s[i]为之前的sum为i的个数,add为上一个小于sum(1, i-1)的个数,对于当前的sum,

如果它要加1,add += s[sum], sum++

如果它要减1,sum –, add -= s[sum]

这样得出的add就是当前的小于sum(1, i)的个数


对于那个优化的解释,我找到了一个更为浅显易懂的版本:
now表示前缀和,delta表示前面有多个位置可以满足区间和大于0,sum[x]表示前缀和为x的个数,那么新加入一个1时,delta显然会增加sum[now]个,然后now++。 加入一个-1时,delta会减少sum[now-1]个,now–

感谢这位大佬:
https://www.cnblogs.com/hua-dong/p/9291507.html

2个结合起来看应该就差不多了
代码:

#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 1000005
#define LL long long
int a[MAXN],sum[MAXN];
LL ans;
int n,m;
LL f(int x)//定义f(x)为中位数大于等于x的区间个数
{
    memset(sum,0,sizeof(sum));//sum[i]表示前缀和为i的区间个数
    int now=n;//前缀和,这里实际上是用了坐标平移
    LL res=0/*答案*/,delta=0;//前面有多少个位置可以满足区间和大于0
    sum[now]=1;
    for(int i=1;i<=n;i++)
    {
        if(a[i]>=x) delta+=sum[now],now++;//加入一个1时,delta会增加sum[now]个
            else now--,delta-=sum[now];//加入一个-1时,delta会减少sum[now]个
        res+=delta;
        sum[now]++;
    }
    return res;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    printf("%lld\n",f(m)-f(m+1));
}
posted @ 2018-08-21 18:09  Starlight_Glimmer  阅读(1)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end