2016中国大学生程序设计竞赛(长春)-重现赛 题解

Posted DGUT_FLY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2016中国大学生程序设计竞赛(长春)-重现赛 题解相关的知识,希望对你有一定的参考价值。

E.The Fastest runner ms.zhang

给一个图,n点n边,问走过所有点的最优方案(总路程最小;其次,起点的序号最小;再次,终点的序号最小)

网上有代码的其实。。。只是看不惯那冗长的代码和较慢的运行速度,就决定自己写一个

整棵树只有n-1边,这种图的话就是一个环连着几棵子树

s和t之间肯定有1条路只走1次,其他路走2次

最短路程有2种情况:

s和t最短路不过环,则st最短路全程在树上,环只走1次,设dis(s,t)是s和t间最短路,looplength是环长度,则答案是2*n-looplength-dis(s,t)

这部分树dp就能解决,不过要注意最大次大值的取值问题(最后的WA就是因为这个)和序号的最小化

s和t最短路过环,则st通路有环部分,由于这种情况环部分的路径不可能覆盖全环,没有被通路覆盖的部分路必须走2次

由于只要覆盖全点,走2次那部分有1条边不用走,少走2次

通路可以选择上面或下面,这样就要选择最长覆盖以减少走2次的路

求这个要用扫描线o(n)的方法防超时

求出区间前缀(不横跨环上0点)和后缀(横跨环上0点)里面元素的最大值与扫描线的距离

距离是dep[i]+i+dep[j]-j,就是说随着扫描线和区间内最大值的变化,要加减的值是按它们序号来的

同样要注意序号最小化

被坑了2天,真不该(坑点:序号最小化)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<math.h>
#include<vector>
#include<map>
#include<set>
#include<stdlib.h>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
using namespace std;
typedef __int64 ll;
const int N=200025;
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int cnt;
int head[N],inu[N],circ[N];
int dep[N][2],pto[N][2],cto[N][2];
int que[N],lop[N];
int to[N<<1],nxt[N<<1];
void addedge(int u,int v)
{
    to[cnt]=v;nxt[cnt]=head[u];head[u]=cnt++;
    to[cnt]=u;nxt[cnt]=head[v];head[v]=cnt++;
}
//相同路径数的索引号选择
void isvalid(int& a,int& b,int c,int d)
{
    int cmpr=min(a,b)-min(c,d);
    if(cmpr>0 || (cmpr==0 && max(a,b)>max(c,d)))
        a=c,b=d;
}
void bfs(int n)
{
    memset(dep,0,sizeof(dep));
    memset(circ,0,sizeof(circ));
    int tai=0,hed=0,i,u,v;
    for(i=0;i<n;i++)
    {
        if(inu[i]==1)que[tai++]=i;
        pto[i][0]=pto[i][1]=cto[i][0]=cto[i][1]=i;//深度为0,则端点就是本身
    }
    while(tai!=hed)
    {
        u=que[hed++];inu[u]--;
        if(circ[u]==dep[u][0]+dep[u][1])
        {
            isvalid(cto[u][0],cto[u][1],pto[u][0],pto[u][1]);
        }
        else if(circ[u]<dep[u][0]+dep[u][1])
        {
            circ[u]=dep[u][0]+dep[u][1];
            cto[u][0]=pto[u][0];
            cto[u][1]=pto[u][1];
        }
        for(i=head[u];i!=-1;i=nxt[i])
        {
            v=to[i];
            if(inu[v]<=0)continue;
            if(dep[v][0]<dep[u][0]+1)
            {
                if(dep[v][1]<dep[v][0])
                {
                    dep[v][1]=dep[v][0];
                    pto[v][1]=pto[v][0];
                }
                else if(dep[v][1]==dep[v][0] && pto[v][1]>pto[v][0])
                {
                    pto[v][1]=pto[v][0];
                }
                dep[v][0]=dep[u][0]+1;
                pto[v][0]=pto[u][0];
            }
            else if(dep[v][0]==dep[u][0]+1 && pto[v][0]>pto[u][0])
            {
                pto[v][0]=pto[u][0];
            }
            else if(dep[v][1]<dep[u][0]+1)
            {
                dep[v][1]=dep[u][0]+1;
                pto[v][1]=pto[u][0];
            }
            else if(dep[v][1]==dep[u][0]+1 && pto[v][1]>pto[u][0])
            {
                pto[v][1]=pto[u][0];
            }
            if(circ[v]==circ[u])
            {
                isvalid(cto[v][0],cto[v][1],cto[u][0],cto[u][1]);
            }
            else if(circ[v]<circ[u])
            {
                circ[v]=circ[u];
                cto[v][0]=cto[u][0];
                cto[v][1]=cto[u][1];
            }
            if(--inu[v]==1)
            {
                que[tai++]=v;
            }
        }
    }
}
int main()
{
    int t,n,u,v,ans,bestu,bestt,i;
    scanf("%d",&t);
    for(int h=1;h<=t;h++)
    {
        ans=99999999;bestu=99999999;bestt=99999999;
        memset(head,-1,sizeof(head));cnt=0;
        memset(inu,0,sizeof(inu));
        scanf("%d",&n);
        for(i=0;i<n;i++)
        {
            scanf("%d%d",&u,&v);u--,v--;
            addedge(u,v),inu[u]++,inu[v]++;
        }
        bfs(n);
        int lpl=0,max1=-99999999,pmax1=99999999,max2=-99999999,pmax2=99999999;
        for(i=0;inu[i]==0;i++);
        for(u=i;inu[u]>=2;u=to[v])
        {
            lop[lpl++]=u;
            for(v=head[u];v!=-1 && inu[to[v]]<2;v=nxt[v]);
            if(v==-1)break;
            inu[u]--;
        }
        lop[lpl]=lop[0];
        for(i=0;i<lpl;i++)
        {
            if(circ[lop[i]]==dep[lop[i]][0]+dep[lop[i]][1])
            {
                isvalid(cto[lop[i]][0],cto[lop[i]][1],pto[lop[i]][0],pto[lop[i]][1]);
            }
            else if(circ[lop[i]]<dep[lop[i]][0]+dep[lop[i]][1])
            {
                circ[lop[i]]=dep[lop[i]][0]+dep[lop[i]][1];
                cto[lop[i]][0]=pto[lop[i]][0];
                cto[lop[i]][1]=pto[lop[i]][1];
            }
        }
        for(i=0;i<lpl;i++)
        {
            int tmp=2*n-lpl-circ[lop[i]];//s and t are put in the same trees
            if(ans==tmp)
            {
                isvalid(bestu,bestt,cto[lop[i]][0],cto[lop[i]][1]);
            }
            else if(ans>tmp)
            {
                ans=tmp;
                bestu=cto[lop[i]][0];
                bestt=cto[lop[i]][1];
            }
            //notice:The loop can walk without 1 edge
            if(pmax1!=99999999)
            {
                tmp=2*n-(i+dep[lop[i]][0]+max1)-2;
                if(ans==tmp)
                {
                    isvalid(bestu,bestt,pto[lop[i]][0],pto[lop[pmax1]][0]);
                }
                else if(ans>tmp)
                {
                    ans=tmp;
                    bestu=pto[lop[i]][0];
                    bestt=pto[lop[pmax1]][0];
                }
            }
            if(max1<dep[lop[i]][0]-i)
            {
                max1=dep[lop[i]][0]-i;
                pmax1=i;
            }
            else if(max1==dep[lop[i]][0]-i && pto[lop[pmax1]][0]>pto[lop[i]][0])
            {
                pmax1=i;
            }
            if(pmax2!=99999999)
            {
                tmp=2*n-(lpl-i+dep[lop[lpl-i-1]][0]+max2)-2;
                if(ans==tmp)
                {
                    isvalid(bestu,bestt,pto[lop[lpl-i-1]][0],pto[lop[pmax2]][0]);
                }
                else if(ans>tmp)
                {
                    ans=tmp;
                    bestu=pto[lop[lpl-i-1]][0];
                    bestt=pto[lop[pmax2]][0];
                }
            }
            if(max2<dep[lop[lpl-i-1]][0]+i)
            {
                max2=dep[lop[lpl-i-1]][0]+i;
                pmax2=lpl-i-1;
            }
            else if(max2==dep[lop[lpl-i-1]][0]+i && pto[lop[pmax2]][0]>pto[lop[lpl-i-1]][0])
            {
                pmax2=lpl-i-1;
            }
        }
        printf("Case #%d: %d %d %d\\n",h,ans,min(bestu,bestt)+1,max(bestu,bestt)+1);
    }
    return 0;
}
View Code

 

G.Instability

给一个图,问最少3个元素,含独立集或团的集合有多少个

其实很简单的,6个点的话,不是含团就是含独立集

枚举3~5个点的集合,时间复杂度最多c(50,3)+c(50,4)+c(50,5)<c(50,5)*3=2118760*3

然后就AC了

 

I. Sequence II

题意:不修改数列的数,在线查询区间中位数

题解:这种没有修改的当然是主席树大法好,为了这个还专门学了一点时间

这个链接http://www.cnblogs.com/zyf0163/p/4749042.html最好懂

就是把第k数据转换为arc(a(k)),然后作为元素插入树k~树n的第arc(a(k))个结点

由于存在大量同构树,可以直接连接而不是新建树结点,这样就节省了memory&&time

至于中位数的查询,先查询[l,r]下有多少不同的数->u,再从l开始查第ceil(u/2)大数

/* ***********************************************
┆  ┏┓   ┏┓ ┆
┆┏┛┻━━━┛┻┓ ┆
┆┃       ┃ ┆
┆┃   ━   ┃ ┆
┆┃ ┳┛ ┗┳ ┃ ┆
┆┃       ┃ ┆
┆┃   ┻   ┃ ┆
┆┗━┓ 马 ┏━┛ ┆
┆  ┃ 勒 ┃  ┆      
┆  ┃ 戈 ┗━━━┓ ┆
┆  ┃ 壁     ┣┓┆
┆  ┃ 的草泥马  ┏┛┆
┆  ┗┓┓┏━┳┓┏┛ ┆
┆   ┃┫┫ ┃┫┫ ┆
┆   ┗┻┛ ┗┻┛ ┆
************************************************ */
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <bitset>
using namespace std;

#define rep(i,a,b) for (int i=(a),_ed=(b);i<=_ed;i++)
#define per(i,a,b) for (int i=(b),_ed=(a);i>=_ed;i--)
#define pb push_back
#define mp make_pair
const int inf_int = 2e9;
const long long inf_ll = 2e18;
#define inf_add 0x3f3f3f3f
#define mod 1000000007
#define LL long long
#define ULL unsigned long long
#define MS0(X) memset((X), 0, sizeof((X)))
#define SelfType int
SelfType Gcd(SelfType p,SelfType q){return q==0?p:Gcd(q,p%q);}
SelfType Pow(SelfType p,SelfType q){SelfType ans=1;while(q){if(q&1)ans=ans*p;p=p*p;q>>=1;}return ans;}
#define Sd(X) int (X); scanf("%d", &X)
#define Sdd(X, Y) int X, Y; scanf("%d%d", &X, &Y)
#define Sddd(X, Y, Z) int X, Y, Z; scanf("%d%d%d", &X, &Y, &Z)
#define reunique(v) v.resize(std::unique(v.begin(), v.end()) - v.begin())
#define all(a) a.begin(), a.end()
#define   mem(x,v)      memset(x,v,sizeof(x))
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
typedef vector<int> vi;
typedef vector<long long> vll;
inline int read(){int ra,fh;char rx;rx=getchar(),ra=0,fh=1;while((rx<\'0\'||rx>\'9\')&&rx!=\'-\')rx=getchar();if(rx==\'-\')fh=-1,rx=getchar();while(rx>=\'0\'&&rx<=\'9\')ra*=10,ra+=rx-48,rx=getchar();return ra*fh;}

const int N = 2e5 + 5;

struct node
{
    int l,r,sum;
}tree[N*40];

int tot,cnt;

void update(int l,int r,int &x,int y,int k,int val)
{
    int tmp = x;
    x = ++tot;
    tree[x] = tmp ? tree[tmp] : tree[y];
    tree[x].sum += val;
    if(l==r) return;
    int mid = (l+r) >> 1;
    if(k<=mid) update(l,mid,tree[x].l,tree[y].l,k,val);
    else update(mid+1,r,tree[x].r,tree[y].r,k,val);
}

int query(int l,int r,int x,int k)
{
    if(l==r) return l;
    int mid = (l+r) >> 1;
    int sum = tree[tree[x].l].sum;
    if(k<=sum) return query(l,mid,tree[x].l,k);
    else return query(mid+1,r,tree[x].r,k-sum);
}

int getsum(int l,int r,int x,int L,int R)
{
    if(L<=l && r<=R) return tree[x].sum;
    int mid = (l+r) >> 1;
    int res = 0;
    if(L<=mid) res += getsum(l,mid,tree[x].l,L,R);
    if(R>mid) res += getsum(mid+1,r,tree[x].r,L,R);
    return res;
}

int a[N],pos[N],rt[N];
int ans[N];

void init()
{
    tot = 0;
    MS0(pos);
    MS0(rt);
}


int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    //ios::sync_with_stdio(0);
    //cin.tie(0);
    int cas = 1;
    int t = read();
    while(t--)
    {
        int n = read(), m = read();
        init();
        for(int i=1;i<=n;i++) a[i] = read();
        for(int i=n;i;i--)//倒着插入,以便从l开始计数
        {
            if(pos[a[i]])
            {
                update(1,n,rt[i],rt[i+1],pos[a[i]],-1);//去除重复的数
            }
            update(1,n,rt[i],rt[i+1],i,1);//前缀树继承上一次连接的
            pos[a[i]] = i;
        }
        for(int i=1;i<=m;i++)
        {
            int l = read(), r = read();
            l = (l + ans[i-1]) % n + 1;
            r = (r + ans[i-1]) % n + 1;
            if(l>r) swap(l,r);
            int sum = (getsum(1,n,rt[l],l,r) + 1) / 2;//向上取整,题目要求
            ans[i] = query(1,n,rt[l],sum);
        }
        printf("Case #%d:",cas++);
        for(int i=1;i<=m;i++) printf(" %d",ans[i]);
        printf("\\n");
    }


    return 0;
}
View Code

 

J. Ugly problem

题意:把大数字拆成不超过50个的回文数

题解:每次用不超过大数字的回文数去减,得到的结果要再这样执行,注意"10"这个数的坑点

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
using namespace std;
const double eps=1e-8;
int len;
//大数乘法
void subtract(char *a,char *b,char *c)
{
    for(int i=len-1;i>=0;i--)
    {
        c[i]=a[i]-b[i]+\'0\';
        if(c[i]<\'0\')c[i]+=10,a[i-1]--;
    }
}

char str[2][1005],sub[50][1005];
int main()
{
    int i,j,k,n;
    scanf("%d",&n);getchar();
    for(int h=1;h<=n;h++)
    {
        memset(sub,\'0\',sizeof(sub));
        gets(str[0]);
        len=strlen(str[0]);
        int old=1,now=0;
        int cnt=0;
        for(i=0;str[now][i];)//前置0到末尾,整个数就是0
        {
            old^=1,now^=1;//滚动数组
            if(!strcmp(str[old]+i,"10"))//10=9+1
            {
                sub[cnt][0]=\'9\';sub[cnt][1]=0;cnt++;
                sub[cnt][0]=\'1\';sub[cnt][1]=0;cnt++;
                break;
            }
            for(j=i;j<=len-1-j+i && str[old][j]<=str[old][len-1-j+i];j++);
            strcpy(sub[cnt],str[old]);
            if(j<len-1-j+i)//对折后有小于的,就只能取较小的回文数
            {
                sub[cnt][(i+len-1)/2]--;
                for(j=(i+len-1)/2;j>=0 && sub[cnt][j]<\'0\';j--)sub[cnt][j]+=10,sub[cnt][j-1]--;
            }
            for(j=0;sub[cnt][j]==\'0\';j++);//去前导0
            for(k=j;k<=len-1-k+j;k++)sub[cnt][len-1-k+j]=sub[cnt][k];//根据前半串对折构造后半串
            sub[cnt][len]=0;
            subtract(str[old],sub[cnt],str[now]);
            for(;str[now][i]==\'0\';i++);
            cnt++;
        }
        printf("Case #%d:\\n%d\\n",h,cnt);
        for(i=0;i<cnt;i++)
        {
            char *s;
            for(s=sub[i];*s==\'0\';s++);
            puts(s);
        }
    }
    return 0;
}
ugly problem

 K.Binary Indexed Tree

题意:分别对树状数组执行add(r,1)和add(l-1,-1)之后,发生改变的结点数量是s(l,r)

求[1,n]里所有区间的s(l,r)和

题解:发现相同右区间的那些s(l,r)的总和有规律

如12,在右区间是12~15时贡献是12,到了>=16的右区间,贡献变成4

可以发现贡献分量与二进制有关这不废话吗!为什么非要暴力计算不可呢

可以理解为异或,把不同二进制数1的个数统计起来,然后答案其实很明显:

\\sum_{r=1}^{n}\\sum_{l=0}^{r-1}(cnt_l+cnt_r-2cnt_{lcp(l,r)})=\\frac{1}{2}\\sum_{l=0}^{n}\\sum_{r=0}^{n}(cnt_l+cnt_r-2cnt_{lcp(l,r)})r=1n​​l=0r1​​(cntl​​+cntr​​2cntlcp(l,r)​​)=21​​l=0n​​r=0n​​(cntl​​+cnt​r​​−2cnt​lcp(l,r)​​)

其中cnt(l)是l作为二进制时1的个数  lcp(l,r)指二进制时l和r的最长公共前缀

减2次lcp(l,r)是异或,减1次的那种是位或运算

为什么直接就是1/2?l和r相等的情况直接被减成0了,就不用考虑容斥

转换成这个,什么都好算

前面2个就是照规律计数,后面打表出来发现规律也可以算。。。不就是把前面的一维计数对应部位平方吗

这个方法最简单了,不像其他方法需要复杂的计算步骤