2016中国大学生程序设计竞赛(ccpc 杭州)题解报告

来源:互联网 发布:linux 创建新用户 编辑:程序博客网 时间:2024/05/02 21:05

此文章可以使用目录功能哟↑(点击上方[+])

感谢ccpc杭州,让我发现了自己模板中的一个错误,笑cry...

链接→2016年中国大学生程序设计竞赛(杭州)-重现赛

 Problem 1001 ArcSoft's Office Rearrangement

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

 Problem Description

ArcSoft, Inc. is a leading global professional computer photography and computer vision technology company.

There are N working blocks in ArcSoft company, which form a straight line. The CEO of ArcSoft thinks that every block should have equal number of employees, so he wants to re-arrange the current blocks intoK new blocks by the following two operations:

- merge two neighbor blocks into a new block, and the new block's size is the sum of two old blocks'.

- split one block into two new blocks, and you can assign the size of each block, but the sum should be equal to the old block.

Now the CEO wants to know the minimum operations to re-arrange current blocks intoK block with equal size, please help him.

 Input

First line contains an integer T, which indicates the number of test cases.

Every test case begins with one line which two integers N and K, which is the number of old blocks and new blocks.

The second line contains N numbers a1, a2, ⋯, aN, indicating the size of current blocks.

Limits

1≤T≤100

1≤N≤10^5

1≤K≤10^5

1≤ai≤10^5

 Output

For every test case, you should output 'Case #x: y', where x indicates the case number and counts from 1 and y is the minimum operations.

If the CEO can't re-arrange K new blocks with equal size, y equals -1.

 Sample Input

3
1 3
14
3 1
2 3 4
3 6
1 2 3

 Sample Output

Case #1: -1
Case #2: 2
Case #3: 3

 Problem Idea

解题思路:

【题意】

一开始有N个分块,每个分块有ai(1≤i≤N)个人,现在要重新进行分块,划分为K个分块,且每个块内人数相同

划分操作有以下两种:

①合并相邻两个分块,合并后块内的总人数等于两分块人数之和

②将一块分割成两个新块,你可以任意分割,只需满足分割后两个新块的总人数等于原分块

问最少需要多少步操作能够做到上述要求,做不到输出-1

【类型】
暴力贪心
【分析】

显然,若总人数无法被K整除,肯定是做不到题目要求的

那么除此之外其他情况的最少操作次数该怎么计算呢?

一开始没有注意到合并操作是对相邻块进行的,感觉有点难,毕竟,人数不够的块应该从哪一块去取来得较优不好判断

考虑到当初现场赛做出此题的人较多,所以重新看了一下题,发现合并是对相邻块操作的

那问题就简单了许多

因为只能对相邻块进行操作,那我们可以从左往右处理,第一块不够,就与第二块合并;多出来就循环拆出sum/K个组成一块

于是遍历一遍就可以解决了

【时间复杂度&&优化】
O(n)

题目链接→HDU 5933 ArcSoft's Office Rearrangement

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<list>#include<bitset>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define PI acos(-1.0)#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 100005;const int M = 100005;const int inf = 1000000007;const int mod = 1000000007;int s[N];int main(){    int t,n,i,p=1;    __int64 k,sum,c,ans;    scanf("%d",&t);    while(t--)    {        ans=c=sum=0;        scanf("%d%I64d",&n,&k);        for(i=1;i<=n;i++)        {            scanf("%d",&s[i]);            sum+=s[i];        }        printf("Case #%d: ",p++);        if(sum%k)        {            puts("-1");            continue;        }        k=sum/k;        for(i=1;i<=n;i++)        {            c+=s[i];            while(c>k)                ans++,c-=k;            if(c==k)                c=0;            else                ans++;        }        printf("%I64d\n",ans);    }    return 0;}

 Problem 1002 Bomb

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)

 Problem Description

There are N bombs needing exploding.

Each bomb has three attributes: exploding radius ri, position (xi,yi) and lighting-cost ci which means you need to pay ci cost making it explode.

If a un-lighting bomb is in or on the border the exploding area of another exploding one, the un-lighting bomb also will explode.

Now you know the attributes of all bombs, please use theminimum cost to explode all bombs.

 Input

First line contains an integer T, which indicates the number of test cases.

Every test case begins with an integers N, which indicates the numbers of bombs.

In the following N lines, the ith line contains four intergers xi, yi, ri and ci, indicating the coordinate of ith bomb is (xi,yi), exploding radius is ri and lighting-cost is ci.

Limits

- 1≤T≤20

- 1≤N≤1000

- −108≤xi,yi,ri≤10^8

- 1≤ci≤10^4

 Output

For every test case, you should output 'Case #x: y', where x indicates the case number and counts from 1 and y is the minimum cost.

 Sample Input

1
5
0 0 1 5
1 1 1 6
0 1 1 7
3 0 2 10
5 0 1 4

 Sample Output

Case #1: 15

 Problem Idea

解题思路:

【题意】

有N个炸弹,第i个炸弹在位置(xi,yi)处,它爆炸波及的范围为其所在位置半径为ri的圆,引爆该炸弹需要的花费为ci

已知一个炸弹爆炸能够引爆其它[中心在该炸弹波及范围内(包含圆上)的]炸弹

问引爆所有炸弹的最小花费是多少

【类型】
强连通缩点(Tarjan+缩点)
【分析】

首先,想也不用想就要做的事是建图

一个炸弹有圆心和半径,处理起来肯定不方便,所以我们要把它们缩小成图中的一个点(当然,这不是上面提到的缩点╮(╯_╰)╭)以方便处理

而炸弹u爆炸如果能引爆炸弹v的话,我们就建一条u->v的有向边

于是炸弹爆炸就可以处理成一幅有向图

接着,我们来讲一下怎么做

一开始,博主没有直接想到强连通缩点,而是采用了自己的一个想法

先将入度为0的炸弹引爆,为什么要引爆入度为0的炸弹呢?

其实稍微想一下就可以明白的,因为入度为0的炸弹不可能由其它炸弹来引爆,若有,入度不可能为0

接着由引爆的点出发搜索一遍,将能够爆炸的点通通标记

而剩下没有爆炸的点就在一个环内,那我只要挑选环内花费最小的炸弹引爆即可

感觉合情合理,交了一发,WA,想了一下,发现还是想简单了,下面这种情况就不符合


上图就是我处理完入度为0的结点后所谓的"环",这个时候,如果我挑选出花费最小的点是D的话,就会导致我结果错误

虽然点D花费最小,但引爆点D是不必要的,引爆它无法引爆A,B,C,导致我们多引爆了一个炸弹

好吧,改改,既然不能引爆环外的点,那我就将环外的点剪掉,反正引爆环内的点就可以顺带引爆环外的点

于是,我将出度为0的点也给去掉了,然后挑选环内花费最小的炸弹引爆

看着可行,又交了一发,还是WA,/(ㄒoㄒ)/~~,受挫

再想想,又发现了反例


如图,在去出度为0的点时,我是无法把D断掉的,而我此时如果选了花费最小的D,会导致重蹈覆辙,好吧,我知错了

提供上图数据,供大家检测

INPUT

5
2 0 2 5
2 2 2 6
2 1 2 7
0 0 1 8
0 -1 1 9

OUTPUT

5

只能老老实实用强连通缩点了

用Tarjan算法模板跑一遍,计算出所有的强连通分量,对于一个强连通分量,由于内部点都是相互连通的,所以任意一个被引爆都可以引爆整个强连通分量,而我们只需将入度为0的强连通分量内花费最小的炸弹引爆,就可以将其相通的一系列炸弹通通引爆。

换句话说,如果一个强连通分量是有入边的,那么就说明这个分量中的所有点都是不需要人为引爆的(可以由入边的分量中任意一个炸弹爆炸来引爆它们),所以最后的答案就是对于每个入度为0的分量中的最小代价点求和。

【时间复杂度&&优化】
O(n^2)

题目链接→HDU 5934 Bomb

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<list>#include<bitset>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define PI acos(-1.0)#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 1005;const int M = 100005;const int inf = 1000000007;const int mod = 1000000007;struct bomb{    int x,y,r;}bob[N];struct edge{    int v,to;}e[N*N];int p,k,h[N],dfn[N],low[N],belong[N],ans;bool instack[N];stack<int> s;int c[N],Min[N],in[N];void add_edge(int u,int v){    e[p].v=v;    e[p].to=h[u];    h[u]=p++;}void Tarjan(int u)//Tarjan算法求有向图的强连通分量,时间复杂度O(n+m){    int i,v,Top;    dfn[u]=low[u]=++k;    s.push(u);//入栈    instack[u]=true;//标记在栈中    for(i=h[u];i+1;i=e[i].to)    {//枚举v的每一条边        v=e[i].v;//v所邻接的边        if(!dfn[v])        {//未被访问            Tarjan(v);//继续向下找            low[u]=min(low[u],low[v]);//更新结点v所能到达的最小次数层        }        else if(instack[v])            low[u]=min(dfn[v],low[u]);    }    if(low[u]==dfn[u])    {        ans++;        while(!s.empty())        {            Top=s.top();            s.pop();            belong[Top]=ans;//出栈结点t属于cnt标号的强连通分量            Min[ans]=min(Min[ans],c[Top]);//强连通分量里花费最小的            instack[Top]=false;//标记不在栈中            if(Top==u)                break;        }    }}bool judge(int a,int b){    return 1ll*(bob[a].x-bob[b].x)*(bob[a].x-bob[b].x)+1ll*(bob[a].y-bob[b].y)*(bob[a].y-bob[b].y)<=1ll*bob[a].r*bob[a].r;}int main(){    int t,n,i,j,answer,cas=1;    scanf("%d",&t);    while(t--)    {        answer=ans=k=p=0;        while(!s.empty())            s.pop();        memset(h,-1,sizeof(h));        memset(in,0,sizeof(in));        memset(instack,false,sizeof(instack));        memset(dfn,0,sizeof(dfn));        memset(belong,0,sizeof(belong));        scanf("%d",&n);        for(i=1;i<=n;i++)        {            scanf("%d%d%d%d",&bob[i].x,&bob[i].y,&bob[i].r,&c[i]);            Min[i]=inf;        }        for(i=1;i<=n;i++)            for(j=1;j<=n;j++)                if(i!=j)                    if(judge(i,j))                        add_edge(i,j);        for(i=1;i<=n;i++)            if(!dfn[i])                Tarjan(i);        for(i=1;i<=n;i++)            for(j=h[i];j+1;j=e[j].to)                if(belong[i]!=belong[e[j].v])                    in[belong[e[j].v]]++;        for(i=1;i<=ans;i++)            if(!in[i])                answer+=Min[i];        printf("Case #%d: %d\n",cas++,answer);    }    return 0;}

 Problem 1003 Car

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

 Problem Description

Ruins is driving a car to participating in a programming contest. As on a very tight schedule, he will drive the car without any slow down, so the speed of the car is non-decrease real number.

Of course, his speeding caught the attention of the traffic police. Police record N positions of Ruins without time mark, the only thing they know is every position is recorded at an integer time point and Ruins started at 0.

Now they want to know the minimum time that Ruins used to pass the last position. 

 Input

First line contains an integer T, which indicates the number of test cases.

Every test case begins with an integers N, which is the number of the recorded positions.

The second line contains N numbers a1, a2, ⋯, aN, indicating the recorded positions.

Limits

1≤T≤100

1≤N≤10^5

0<ai≤10^9


 Output

For every test case, you should output 'Case #x: y', where x indicates the case number and counts from 1 and y is the minimum time.

 Sample Input

1
3
6 11 21

 Sample Output

Case #1: 4

 Problem Idea

解题思路:

【题意】

一辆车从0时刻从位置0开始行驶,车速是非递减的实数

现在记录了车行驶过程中N个整数时刻的位置

问车经过第N个位置的最少需要多少时间

【类型】
贪心
【分析】

由于车速是非递减的,那么从第N-1个位置到第N个位置最优的用时为1

对于相邻的两段,我们有

稍微做个转化,可得

即相同时间间隔内,后者所驶过的路程要大于等于前者

已知最后一段行驶时间间隔为1,行驶路程为a[n]-a[n-1]

倒数第二段行驶路程为a[n-1]-a[n-2]

则倒数第二段的行驶时间间隔为



得到倒数第二项之后可以由倒数第二项推出倒数第三项

反向遍历一遍即可

【时间复杂度&&优化】
O(n)

题目链接→HDU 5935 Car

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<list>#include<bitset>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define PI acos(-1.0)#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 100005;const int M = 100005;const int inf = 1000000007;const int mod = 1000000007;int s[N];int main(){    int t,n,i,j,p=1;    __int64 ans,a,b,c;    scanf("%d",&t);    while(t--)    {        ans=0;        scanf("%d",&n);        for(i=1;i<=n;i++)            scanf("%d",&s[i]);        a=s[n]-s[n-1];b=1;        for(i=n;i>=1;i--)        {            c=s[i]-s[i-1];            b=(b*c+a-1)/a;            a=c;            ans+=b;        }        printf("Case #%d: %I64d\n",p++,ans);    }    return 0;}

 Problem 1006 Four Operations

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

 Problem Description

Little Ruins is a studious boy, recently he learned the four operations!

Now he want to use four operations to generate a number, he takes a string which only contains digits '1' - '9', and split it into 5 intervals and add the four operations '+', '-', '*' and '/' in order, then calculate the result(/ used as integer division).

Now please help him to get the largest result.

 Input

First line contains an integer T, which indicates the number of test cases.

Every test contains one line with a string only contains digits '1'-'9'.

Limits

1≤T≤10^5

5≤length of string≤20

 Output

For every test case, you should output 'Case #x: y', where x indicates the case number and counts from 1 and y is the result.

 Sample Input

1
12345

 Sample Output

Case #1: 1

 Problem Idea

解题思路:

【题意】

现有一个只含数字'1'~'9'的字符串,5≤字符串长度≤20

我们要将字符串从左到右分成五部分,相邻两部分依次放入'+','-','*','/'四种运算符,问式子最大结果为多少

【类型】
贪心+dfs爆搜->dfs爆搜+剪枝
【分析】

这题官方的做法会来得更优一些,但我还是会讲讲自己的做法

先看官方解法:

假设这个式子可以写成

y=a+b−c×d / e
对于式子中的后半部分c×d / e, c和d应该只保留一位,让最后的答案最小。而对于e,它最多只会有两位,这是因为c×d<100并且,如果你用了三位让后半部分从一个一位数变成了0,还不如省下那一位让前半部分尽量大。
对于前半部分,a和b一定是一个一位数加一个多位数。所以我们只要枚举加号和除号的位置就可以定下来最后式子长什么样了。
总体复杂度:O(2×2×n)

个人解法:一开始,无脑爆搜,由于组数较大,TLE了

于是选择剪剪枝,假设式子写成y=a+b−c×d / e的形式,由于×和/的运算优先级较高,所以会先运算后面部分,然后计算前面部分

那么在乘法和除法进行完之后(令z=c×d / e),原式变为y=a+b-z,为了使y尽可能大,我们肯定是让a和b尽可能大,z尽可能小

而z要尽可能小,显然c和d要尽可能小,e极可能大。

由于总位数一定,故各自多长就得有取舍

本人的剪枝是使c和d仅有一位,也就是在dfs搜索过程中,一旦我放了'-',那我在下一间隔立马放'×',再下一间隔立马放'/',这样就可以少搜很多次

剪枝之前,对于长度为20的字符串"99999999999999999999"需要搜上万次


在剪枝之后,对于长度为20的字符串"99999999999999999999"也只要搜一千多次


【时间复杂度&&优化】
<O(1463T)

题目链接→HDU 5938 Four Operations

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<list>#include<bitset>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define PI acos(-1.0)#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 30;const int M = 100005;const int inf = 1000000007;const int mod = 1000000007;char s[N],ch[N];__int64 ans;int len;void dfs(int k,int flag){    //printf("%d %d\n",k,flag);    if(len-k<4-flag)        return ;    if(k==len)    {        if(flag==4)        {            int c=0;            __int64 w[5];            memset(w,0,sizeof(w));            for(int i=0;i<k+4;i++)                if(ch[i]>='0'&&ch[i]<='9')                    w[c]=w[c]*10+ch[i]-'0';                else                    c++;            //printf("%I64d %I64d %I64d %I64d %I64d\n",w[0],w[1],w[2],w[3],w[4]);            ans=max(ans,w[0]+w[1]-w[2]*w[3]/w[4]);            //printf("#%I64d\n",ans);        }        return ;    }    ch[k+flag]=s[k];    if(flag>1&&flag<4&&k+1<len)    {        ch[k+flag+1]=0;        dfs(k+1,flag+1);    }    else    {        dfs(k+1,flag);        if(flag<2&&k+1<len)        {            ch[k+flag+1]=0;            dfs(k+1,flag+1);        }    }}int main(){    int t,p=1;    scanf("%d",&t);    while(t--)    {        ans=-1e18;        scanf("%s",&s);        len=strlen(s);        dfs(0,0);        printf("Case #%d: %I64d\n",p++,ans);    }    return 0;}

 Problem 1011 Kingdom of Obsession

Accept: 0    Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

 Problem Description

There is a kindom of obsession, so people in this kingdom do things very strictly.

They name themselves in integer, and there are n people with their id continuous (s+1,s+2,⋯,s+n) standing in a line in arbitrary order, be more obsessively, people with id x wants to stand at position which satisfy

x mod y = 0

Is there any way to satisfy everyone's requirement?

 Input

First line contains an integer T, which indicates the number of test cases.

Every test case contains one line with two integers n, s.

Limits

1≤T≤100.

1≤n≤10^9.

0≤s≤10^9.

 Output

For every test case, you should output 'Case #x: y', where x indicates the case number and counts from 1 and y is the result string.

If there is any way to satisfy everyone's requirement, y equals 'Yes', otherwise y equals 'No'.

 Sample Input

2
5 14
4 11

 Sample Output

Case #1: No
Case #2: Yes

 Problem Idea

解题思路:

【题意】

给你s和n

将连续的n个数s+1,s+2,...,s+n随机放置到位置1,2,...,n中,问是否存在一种情况,使得第∀i(1≤i≤n)个位置上的数xi满足xi mod i = 0

换句话说,n 个人编号为 [s + 1 , s + n],有 n 个座位编号为 [1 , n],编号为 i 的人只能坐到编号为它的约数的座位,问每个人是否都有位置坐。

【类型】
匹配,数论
【分析】

对于此题,在讲解之前,需要证明一下,[s+1 , n] 这一段数肯定坐到自己编号的位置上要更好,即下图这种情况


s+1=3,放到3这个位置;s+2=4,放到4这个位置;...以此类推...;s+(n-2)=n,放到n这个位置。

剩下s+(n-1)和s+n,再与位置[1,s]进行匹配

证明:

我们定义bi=s+i(1≤i≤n),如果bi≤n,那么最优的做法一定是把它放在原来自己的位置上

反证法,假设数bi放在了位置p上,而且p<bi,那说明bi mod p = 0,若此时有一个比bi大的数bj能够放在位置bi上(即bj mod bi = 0),那数bj肯定可以放在位置p上;而数bj不能放在位置bi上时,数bj依然有可能可以放在位置p上,即bj mod bi ≠ 0,但bj mod p = 0

所以,为了尽可能多的匹配,显然数bi放在位置bi上来得更优

证毕

那么对于给定n,s,我们只需对数[max(s+1,n+1),s+n]与位置[1,min(s,n)]进行匹配即可

由于质数只能坐到 1 或者它本身的位置上,所以如果 [max(s+1,n+1),s+n] 区间内有多于一个质数时肯定无解。有解时这个区间一定不会很大。对于剩下的min(s,n)个位置只需要用匈牙利算法二分匹配一下即可

那么,什么情况下,[max(s+1,n+1),s+n]区间内会存在两个质数呢?

首先,目前已知的质数中,相邻质数间隔最大为777

所以当需要匹配的对数超过777对,那就可以直接输出"No"了

【时间复杂度&&优化】
O(n^3)//匈牙利算法邻接矩阵实现

题目链接→HDU 5943 Kingdom of Obsession

 Source Code

/*Sherlock and Watson and Adler*/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<stdio.h>#include<string.h>#include<stdlib.h>#include<queue>#include<stack>#include<math.h>#include<vector>#include<map>#include<set>#include<list>#include<bitset>#include<cmath>#include<complex>#include<string>#include<algorithm>#include<iostream>#define eps 1e-9#define LL long long#define PI acos(-1.0)#define bitnum(a) __builtin_popcount(a)using namespace std;const int N = 778;const int M = 100005;const int inf = 1000000007;const int mod = 1000000007;int l,r,link[N];//l为左集合点数,r为右集合点数bool v[N],g[N][N];//编号为0~n-1bool dfs(int u){    int i;    for(i=0;i<r;i++)        if(g[u][i]&&!v[i])        {            v[i]=true;            if(link[i]==-1||dfs(link[i]))            {                link[i]=u;                return true;            }        }    return false;}int hungary(){    int i,res=0;    memset(link,-1,sizeof(link));    for(i=0;i<l;i++)    {        memset(v,false,sizeof(v));        if(dfs(i))            res++;    }    return res;}int main(){    int t,p=1,i,j,n,s;    scanf("%d",&t);    while(t--)    {        memset(g,0,sizeof(g));        scanf("%d%d",&n,&s);        printf("Case #%d: ",p++);        if(s+n-max(s+1,n+1)>=N)        {            puts("No");            continue;        }        for(i=max(s+1,n+1);i<=s+n;i++)            for(j=1;j<=min(s,n);j++)                if(i%j==0)                   g[i-max(s+1,n+1)][j-1]=1;        l=r=min(s,n);        if(hungary()==min(s,n))            puts("Yes");        else            puts("No");    }    return 0;}
菜鸟成长记

3 0
原创粉丝点击