标签:
此文章可以使用目录功能哟↑(点击上方[+])
经过这么一次女生赛,告诉我们千万不要小瞧女生,不然会死得很惨,orz...
链接→"巴卡斯杯" 中国大学生程序设计竞赛 - 女生专场(重现)
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 32768/32768 K (Java/Others)
Welcome to HDU to take part in the first CCPC girls‘ competition!
The first line is an integer T which indicates the case number.
And as for each case, the first line is an integer n, which is the number of problems.
Then there are n lines followed, with a string and an integer in each line, in the i-th line, the string means the color of ballon for the i-th problem, and the integer means the ballon numbers.
It is guaranteed that:
T is about 100.
1≤n≤10.
1≤ string length ≤10.
1≤ bolloon numbers ≤83.(there are 83 teams :p)
For any two problems, their corresponding colors are different.
For any two kinds of balloons, their numbers are different.
For each case, you need to output a single line.
There should be n strings in the line representing the solving order you choose.
Please make sure that there is only a blank between every two strings, and there is no extra blank.
解题思路:
【题意】
打气球的志愿者们比较蠢!
于是,打的气球越多,意味着题目越水>_<。
现在给你气球数,问你按照什么顺序做题,能够得以从易到难的顺序解决所有问题。
【类型】
C语言签到题
【分析】
这题,显然,我们要做的任务只是排序。
如果你刚接触程序设计,甚至不会c++的STL,可以先把气球数排序。然后按照i从小到大枚举,再依次枚举气球j,
if(气球数第i大的 == 当前这个j号气球的数量)then print(这个j号气球的颜色),复杂度O(n^2)。
但是,当然,如果你会STL,最简单的方法,就是按照本程序的方式排个序。这道题目就做完啦啊。
【时间复杂度&&优化】
O(nlogn)
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N = 100005; const int M = 15; const int inf = 100000000; const int mod = 2009; struct balloon { char color[M]; int number; }s[M]; bool cmp(balloon x,balloon y) { return x.number>y.number; } int main() { int t,n,i; scanf("%d",&t); while(t--) { scanf("%d",&n); for(i=0;i<n;i++) scanf("%s%d",s[i].color,&s[i].number); sort(s,s+n,cmp); for(i=0;i<n;i++) printf("%s%c",s[i].color,i!=n-1?' ':'\n'); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/32768 K (Java/Others)
A tourist gets lost in the desert with n liters of water. He drinks positive integer units of water each day.
Write a program to calculate how many di?erent ways the tourist can drink up the water.
Output consists of T lines.
Each line contains the binary number which represents number of different ways to finish up the water specified in the test case.
解题思路:
【题意】
一开始有数量为n(1<=n<=1e6)升的水,
我们每天可以喝数量为任意整数的水,问你有多少种方式可以把水喝完。
【类型】
简单排列组合 隔板法 整数拆分
【分析】
这道题其实就是——
问你,对于整数n,可以把n拆分成多少个不同的正整数序列
对于比赛型选手,显然此题不需要追求什么为什么
写几个找规律才是王道
但是,比赛过后,我们就要深究一下所以然
我们考虑排列组合中的隔板法。
就是n个数,中间有n-1个空位,
其中每一个空位中,都可以选择插入隔板,从而把这n个数分成若干份,恰好对应本题。
显然——
如果拆成1份,方案数是C(n-1,1-1)=C(n-1,0)
如果拆成2份,方案数是C(n-1,2-1)=C(n-1,1)
如果拆成3份,方案数是C(n-1,3-1)=C(n-1,2)
...
如果拆成n份,方案数是C(n-1,n-1)=C(n-1,n-1)
对应着二项式展开,它们的和恰好是2^(n-1)。
于是对应着,我们直接输出——
1个1
+(n-1)个0即可。
【时间复杂度&&优化】
O(n)
题目链接→HDU 5703 Desert
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N = 100005; const int M = 15; const int inf = 100000000; const int mod = 2009; int main() { int t,n,i; scanf("%d",&t); while(t--) { scanf("%d",&n); printf("1"); n--; for(i=0;i<n;i++) printf("0"); puts(""); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/32768 K (Java/Others)
Participants of the Luck Competition choose a non-negative integer no more than 100 in their mind. After choosing their number, let K be the average of all numbers, and M be the result of K×. Then
the lucky person is the one who choose the highest number no more than M. If there are several such people, the lucky person is chosen randomly.
If you are given a chance to know how many people are participating the competition and what their numbers are, calculate the highest number with the highest probability to win assuming that you‘re joining the competition.
For each test case, output an integer which you have chosen and the probability of winning (round to two digits after the decimal point), seperated by space.
解题思路:
【题意】
n(2~100)个人参加一个游戏,
每个人选择1~100范围的数。
然后得到所有数的平均数,再*=2/3,设得到的数为m。
如果一个人选的数,比m小,且相距m最为接近,那么其便在所有选数相同的人中等概率中奖。
现在,我们也参加比赛,其他n-1个人所选择的数也已经确定了,并且我们知道。
问你,选什么数拥有最高中奖率,并输出。
【类型】
公式推导+标记
【分析】
假设我们选的数为x,其他n-1个人选的数之和为sum,那么M的值为
显然,x需要满足
要使x尽可能大,不等式右侧向下取整即可
得到x之后,获奖概率取决于同样选了x的人有几个,因为x的值比较小,我们完全可以在输入的时候保存每种数出现的次数
【时间复杂度&&优化】
O(nT)
题目链接→HDU 5704 Luck Competition
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N = 105; const int M = 15; const int inf = 100000000; const int mod = 2009; int c[N]; int main() {//2(sum+x)/(3n)>=x int t,n,i,sum,x; scanf("%d",&t); while(t--) { sum=0; memset(c,0,sizeof(c)); scanf("%d",&n); for(i=1;i<n;i++) { scanf("%d",&x); c[x]++; sum+=x; } x=2*sum/(3*n-2); printf("%d %.2f\n",x,1.0/(c[x]+1)); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/65535 K (Java/Others)
For each test case, output a single line contains test case number and the answer HH:MM:SS.
解题思路:
【trick&&吐槽】
最终的HH值可能会超过12
所以要对12取余,因为这个,错了n发,其他问题还好
【题意】
时间为12小时制。
告诉你一个时刻,让你输出在这个时刻之后的下一个时刻,
满足:该时刻,时针分针掐好相差某个的角度为a。
(注意,满足要求的时刻不一定是恰好以秒为单位,可以是秒与秒之间的时刻,我们可以向下取整)
【类型】
追及问题,判断
【分析】
如何使得时针与分针的角度恰好相差某个角度呢?
钟表有360个度,
一,考虑时针位置
每小时为30度
每分钟为1/2度
每秒钟为1/120度
于是我们不妨使得所有与角度相关的单位都*=120,转化成整数,告别精度误差。
(其实也就可以看做是为:以秒为基本单位。)
转变为:
每小时为3600°
每分钟为60°
每秒钟为1°
二,考虑分针位置
每分钟为6度
每秒钟为0.1度
我们一样*=120,转变为——
每分钟为720°
每秒钟为12°
首先先将当前时针和分针的位置确定
为了确保满足时针和分针夹角为a的情况不是当前时间,我们先将时间往后拨1秒,即分针走12°,时针走1°
然后判断时针分针的位置关系即可,主要分为下列四种位置关系
1.分针追上时针的夹角为θ>=180°,夹角a<360°-θ
那么时针和分针达到a的时间为(θ-a*120)/11°
2.分针追上时针的夹角为θ>=180°,夹角a≥360°-θ
那么时针和分针达到a的时间为(θ-(360°-a)*120)/11°
3.分针追上时针的夹角为θ<180°,夹角a<=θ
那么时针和分针达到a的时间为(d-a*120)/11°
4.分针追上时针的夹角为θ<180°,夹角a>θ
那么时针和分针达到a的时间为(d+a*120)/11°
【时间复杂度&&优化】
O(T)
题目链接→HDU 5705 Clock
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 #define bitnum(a) __builtin_popcount(a) using namespace std; const int N = 100005; const int M = 120; const int inf = 1600000000; const int mod = 2009; int solve(int u,int v,int a) { int d,ans; d=(v+M*360-u)%(M*360); if(d>=M*180) { if(a*M<360*M-d) ans=v+(d-a*M)/11; else ans=v+(d-(360-a)*M)/11; } else { if(a*M<=d) ans=v+(d-a*M)/11; else ans=v+(d+a*M)/11; } return ans; } int main() { //u=1/10*(m*60+s); //v=1/120*(h*3600+m*60+s); int h,m,s,a,u,v,p=1,ss,mm,hh,ans; while(~scanf("%d:%d:%d%d",&h,&m,&s,&a)) { u=(12*(m*60+s+1))%(M*360); v=h*3600+m*60+s+1; ans=solve(u,v,a); hh=ans/3600%12; mm=ans/60%60; ss=ans%60; printf("Case #%d: %02d:%02d:%02d\n",p++,hh,mm,ss); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 32768/32768 K (Java/Others)
As for each case, you need to output a single line.
There should be 2 integers in the line with a blank between them representing the number of girls and cats respectively.
Please make sure that there is no extra blank.
解题思路:
【题意】
我们拍了一张照片!
照片是n*m个像素的!
对于每个像素,都是一个小写英文字符。
如果连着走4步,是"girl"就是一个girl
如果连着走3步,是"cat"就是一只cat
然后问你,这个图上有多少个girl多少只cat
【类型】
简单bfs or dfs
【分析】
显然,如果你很熟练的话,只要暴力枚举起点,然后做记录匹配阶段的暴力dfs或者bfs即可。
然而,这题的友好之处,在于——
它给并不会算法的人,提供了一个用汗水交换丰收=w=的机会。
你只要写一串for,或者一大通if!就可以顺利获得一个AC啦!
for(i)for(j)
if(a[i][j]==‘g‘)
{
if(a[i-1][j]==‘l‘)
{
if(...)
}
if(a[i+1][j]==‘l‘)
{
if(...)
}
if(a[i][j-1]==‘l‘)
{
if(...)
}
if(a[i][j+1]==‘l‘)
{
if(...)
}
}
就这样 = =
23333
【时间复杂度&&优化】
O(Tnm)
题目链接→HDU 5706 GirlCat
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N = 1005; const int M = 15; const int inf = 100000000; const int mod = 2009; char s[N][N],ch[2][5]={"girl","cat"}; bool v[N][N]; int ans[2],n,m; bool check(int x,int y) { return x>=0&&x<n&&y>=0&&y<m; } void dfs(int x,int y,int k,int t) { if(!check(x,y)||s[x][y]!=ch[t][k]) return; v[x][y]=true; if(t&&k==2||!t&&k==3) { ans[t]++; return; } dfs(x+1,y,k+1,t); dfs(x,y+1,k+1,t); dfs(x-1,y,k+1,t); dfs(x,y-1,k+1,t); } int main() { int t,i,j; scanf("%d",&t); while(t--) { ans[0]=ans[1]=0; memset(v,false,sizeof(v)); scanf("%d%d",&n,&m); for(i=0;i<n;i++) scanf("%s",s[i]); for(i=0;i<n;i++) for(j=0;j<m;j++) if(!v[i][j]) { if(s[i][j]=='g') dfs(i,j,0,0); else if(s[i][j]=='c') dfs(i,j,0,1); } printf("%d %d\n",ans[0],ans[1]); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/65535 K (Java/Others)
Given three strings a, b and c, your mission is to check whether c is the combine string of a and b.
A string c is said to be the combine string of a and b if and only if c can be broken into two subsequences, when you read them as a string, one equals to a, and the other equals to b.
For example, ``adebcf‘‘ is a combine string of ``abc‘‘ and ``def‘‘.
For each test case, print ``Yes‘‘, if c is a combine string of a and b, otherwise print ``No‘‘.
解题思路:
【trick&&吐槽】
这道题不能直接用指针的方法做贪心,即下面的程序是错误的
void greedy_wa()
{
int pa = 1;
int pb = 1;
int pc = 1;
while (pc <= g)
{
if (pa <= n && a[pa] == c[pc])++pa;
else if (pb <= m && b[pb] == c[pc])++pb;
else break;
++pc;
}
puts(pc > g ? "Yes" : "No");
}
因为可能有这样的数据:
abce
abcd
abcdabce
我们本来为了匹配,应当释放正确的前缀,即第二个。
然而我们如果贪心做的话,有可能把首先选择了第一个前缀的abc,这样子就错掉了。
【题意】
给你两个字符串a,b
然后再给你一个字符串c(长度都在2000范围内)
问你——能否恰把c分成两个字符串a,b。
【类型】
DP
【分析】
本题的DP类似于LCS(longest common subsequence)(如果不了解的可以搜一下)。
只是,
f[i][j]表示第一个字符串用了前i个位置(第i个位置已匹配),第二个字符串的前j个位置(第j个位置已匹配)
是否可以对c串成功匹配(成功匹配则必然会匹配到c串的前i+j个位置)。
f[i][j]==1则表示可以成功匹配
f[i][j]==0则表示无法成功匹配
显然,初始只有f[0][0]==1
所以,我们有——
f[i][j]= f[i-1][j]&&(a[i]==c[i+j])
|| f[i][j-1]&&(b[j]==c[i+j])
这样,最终f[n][m]==1则为Yes否则为No
【时间复杂度&&优化】
O(nm)
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 #define bitnum(a) __builtin_popcount(a) using namespace std; const int N = 2005; const int M = 120; const int inf = 1600000000; const int mod = 2009; char a[N],b[N],c[N]; int dp[N][N]; int main() { int i,j,n,m,k; while(~scanf("%s",a+1)) { memset(dp,0,sizeof(dp)); scanf("%s",b+1); scanf("%s",c+1); n=strlen(a+1); m=strlen(b+1); k=strlen(c+1); if(n+m!=k) { puts("No"); continue; } dp[0][0]=1; for(i=0;i<=n;i++) for(j=0;j<=m;j++) { if(i>0&&c[i+j]==a[i]) dp[i][j]|=dp[i-1][j]; if(j>0&&c[i+j]==b[j]) dp[i][j]|=dp[i][j-1]; } /*for(i=0;i<=n;i++) { for(j=0;j<=m;j++) printf("%d ",dp[i][j]); puts(""); }*/ if(dp[n][m]) puts("Yes"); else puts("No"); } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/65536 K (Java/Others)
解题思路:
【trick&&吐槽】
判定奇偶性的时候——
%2==1往往是错的,有时%2==-1。还是&1 来判定奇偶性的方法比较靠谱
【题意】
给你一个棋盘,大小为n*m。
我们一开始在(1,1)点,
如果我们当前在(x,y),
那么我们可以在一步之内走到
(x+1,y)
(x,y+1)
(x+k,y+k)
先告诉你k,k∈[1,1e9]
然后有q组询问,q∈[1,1000]
对于每个q,告诉你(n,m)
n∈[1,1e9],m∈[1,1e9]。
无法可走便输了,问先手必胜还是后手必胜。
【类型】
博弈 打表找规律
【分析】
对于这题,n和m都是1e9,太大了。
于是我们要考虑O(1)出解。
即打表找规律。
至于,什么是打表找规律呢?
1,问题数据规模很小,我们可以本地预处理出所有解,然后存到代表段数组中,O(1)输出
2,问题数据规模很大,我们可以本地跑出小数据的答案,然后猜解大数据的规律。
对于这题——
K==1时,表示这样的:
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
K==2时,表示这样的:
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1
0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0
1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1
0 1 1 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0
0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 0 1
0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 0 1 0
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1
0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0
K==3时,表示这样的:
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
0 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 1 0 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0
0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 1 0 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1
0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1
0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 0 1 0
1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 1 1 1
0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1
1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0
0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1
1 0 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 1
我们发现到,
对于K>1时,
对于f[K][K]~f[K][∞],答案都是1(先手必胜)
对于f[K][K]~f[∞][K],答案都是1(先手必胜)
对于f[2K][2K]~f[2K][∞],答案都是1(先手必胜)
对于f[2K][2K]~f[∞][2K],答案都是1(先手必胜)
对于f[3K][3K]~f[3K][∞],答案都是1(先手必胜)
对于f[3K][3K]~f[∞][3K],答案都是1(先手必胜)
......
即f[nK][nK]~f[nK][∞],答案都是1(先手必胜)
f[nK][∞]~f[nK][nK],答案都是1(先手必胜)
对于其它的f(i,j),我们要找到第一个位置(pK,pK),满足i>=pK且j>=pK。
然后:
如果p是奇数,那么与(pK,pK)同奇偶性的(即横纵坐标之和同奇偶性)都是必胜点,否则为必败点
如果p是偶数,那么与(pK,pK)同奇偶性的(即横纵坐标之和同奇偶性)都是必败点,否则为必胜点
然而,对于K==1的时候,是特例的。不论p是奇数还是偶数,与(pK,pK)同奇偶性的(即横纵坐标之和同奇偶性)都是必败点,否则为必胜点
这里再尝试一下证明:(我们不妨把题目中的由从(1,1)->(n,m)),改变方向为从(n,m)走到(1,1),这样答案不变,然而考虑和看起来更直观。
对于初始K==1的,对于(n,m)==(X,X),初始点(先手)的胜负态为:
(1,1):必败态
(1,x)为(1,x-1)的反态,(x,1)为(x-1,1)的反态,为必胜态和必败态的依次轮转。
(2,2):必胜态
(2,x)和(x,2):都可以找到(1,x)和(1,x-1)的两个前驱,必然有必败态的前驱,所以全为必胜态
接下来所有点都找不到任何一个必败态的前驱,其状态又归回于——
(3,3):必败态
(3,x)为(3,x-1)的反态,(x,3)为(x-1,3)的反态,为必胜态和必败态的依次轮转。
(4,4):必胜态
(4,x)都可以找到(3,x)和(3,x-1)的两个前驱,(4,x)都可以找到(3,x)和(3,x-1)的两个前驱,必然有必败态的前驱,所以全为必胜态
……
所以我们对K==1条件下的结论是正确的
对于初始K>1的,我们不妨使得K+=1,然后,对于(n,m)==(X,X),初始点(先手)的胜负态为:
(1~K-1,x)与(x,1~K-1),这些点都是不涉及到斜着走K步的情况的,于是胜负只与奇偶性有关,即偶点必败奇点必胜。
(K,K):必胜态,因为其可以一步到(1,1)这个必败态。
(K,K~x)和(K~x,K),其既可以达到奇点,又可以达到偶点,所以是必胜态。
就是按照这个过程,奇偶归类,虽然很繁琐,然而我们可以得到结论的证明>_<
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; } const int N = 0, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f; int casenum, casei; int Q, K, n, m; int f[100][100]; int dp(int n, int m) { if (n == 1 && m == 1)return 0; if (~f[n][m])return f[n][m]; if (n > 1 && !dp(n - 1, m))return f[n][m] = 1; if (m > 1 && !dp(n, m - 1))return f[n][m] = 1; if (n > K&&m > K&&!dp(n - K, m - K))return f[n][m] = 1; return f[n][m] = 0; } void table(int k) { K = k; for (n = 1; n <= 20; ++n) { for (m = 1; m <= 20; ++m) { MS(f, -1); printf("%d ", dp(n, m)); }puts(""); } } int main() { //table(1);table(2);table(3); scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { scanf("%d%d", &Q, &K); ++K; //首先先使得K+=1 while (Q--) { scanf("%d%d", &n, &m); //规律1:如果较小宽度为K的倍数,那么先手必胜 if (n > m)swap(n, m); //n<=m if (n%K == 0) { puts("Alice"); continue; } //规律2:距离顶角元素距离的奇偶性,一样对答案有所影响(特判K==2) int len = n / K; if (K > 2 && (len & 1)) { puts((n + m) % 2 == 0 ? "Alice" : "Bob"); } else { puts((n + m) % 2 == 0 ? "Bob" : "Alice"); } } } return 0; }
Accept: 0 Submit: 0
Time Limit: 10000/5000 MS (Java/Others) Memory Limit : 256000/256000 K (Java/Others)
For each query output a single integer in a line, denoting the answer.
解题思路:
【题意】
给你一棵n(1e5)个节点的树
每个节点都有一个颜色
有m(1e5)个询问
对于每个询问,
问你以一个节点为子树的根,距离其深度<=d的所有节点中,有多少种不同颜色。
强制在线。
【类型】
动态节点线段树
【分析】
首先,这道题的解题思想是这样的——
我们做dfs,然后自底向上,维护两个东西。
1,每个颜色对应的最大深度
2,每个深度对应的颜色数
然后,我们对于一个新节点(显然处理这个节点的时候,这个节点是位于父节点的结构位置的)
我们首先,给它建立一棵线段树。在这棵线段树是基于全0线段树展开的。
唯一不同的是,在dep[x]的位置,权值要为1。
于是我们按照这个原理,在不超过logn的复杂度内建立一棵线段树。
然后,我们要涉及到其与子节点的合并操作。
怎么合并的呢?如果有一棵子树为空,那么显然我们直接扔掉。
否则我们就合并左子树,合并右子树。
因为这个是维护深度->节点数,
所以我们合并的时候,直接把节点数相加就好。
我们这样就能够得到:以每个节点为根的子树中,各个深度的颜色数。
然而,在这个条件下,我们的计数是可能发生重复的。就是同一个颜色,被计数两次。
如何维护呢?
我们对于一种颜色,只要维护该颜色的最小深度即可。
这个同样可以用一棵线段树来实现。
也去建一棵线段树,这棵线段树也是基于全0线段树展开的。
只是,在颜色c[x]的位置,权值要为其深度。
这棵线段树虽然说是线段树,然而实际上,其实只是为了维护叶节点而已。
虽然我们看似是一直查找所有的叶节点,
然而,一旦是空子树我们就不向下查找了。
【时间复杂度&&优化】
维护深度-节点数的复杂度是O(nlogn),因为我们每次修改深度
查找叶节点的复杂度为log级别,叶节点的数量为n级别,合并操作复杂度为O(1)
因此总复杂度为O(nlogn)
题目链接→HDU 5709 Claris Loves Painting
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; } const int N = 1e5 + 10; const int M = 1e7; //一个问题是,我们需要多少个节点呢?(n+m)*log(n)*3 about? int casenum, casei; int n, m, x, d; struct Node { int l, r, v; }node[M]; int id; vector<int>a[N]; int c[N]; int dep[N]; int root[N]; //root[x]表示以x为子树根节点的线段树,其线段树用于维护每个深度的颜色 int Root[N]; //Root[x]表示以x为子树根节点的线段树,其线段树用于维护每个每个颜色对应的深度 //该函数用于在原有线段树基础上,实现v[p]+=val操作 int update(int o, int l, int r, int p, int val) { int now = ++id; node[now] = node[o]; node[now].v += val; if (l == r)return now; int mid = (l + r) >> 1; if (p <= mid)node[now].l = update(node[o].l, l, mid, p, val); else node[now].r = update(node[o].r, mid + 1, r, p, val); return now; } //该函数用户合并两棵线段树,对于深度-颜色数线段树,可以维护使得数量累加;对于颜色-深度线段树, int merge(int x, int y, int pos=0) { //合并原则1,如果有子树为空可以直接合并,并不需要做修改 if (!x && !y)return 0; if (!x)return y; if (!y)return x; //合并原则2,两棵子树均非空时,我们需要新建节点做合并 int now = ++id; if (!pos)node[now].v = node[x].v + node[y].v; else if (!node[x].l && !node[x].r) //如果涉及到颜色-深度线段树的合并(即pos不为0),那么我们只有在叶子节点才做删除操作 { //具体的删除操作,是找到深度较大的颜色,把其在深度-数量线段树中删除 node[now].v = min(node[x].v, node[y].v); root[pos] = update(root[pos], 1, n, max(node[x].v, node[y].v), -1); } node[now].l = merge(node[x].l, node[y].l, pos); node[now].r = merge(node[x].r, node[y].r, pos); return now; } void dfs(int x) { root[x] = update(0, 1, n, dep[x], 1); //先在空线段树基础上建立深度-数量线段树,初始化只有dep[x]子树有权值为1 Root[x] = update(0, 1, n, c[x], dep[x]); //然后在空线段树基础上建立颜色-最小深度线段树(该线段树只为了维护叶节点,并没有区间效应),初始化只有颜色c[x]的值为深度dep[x] for (int i = a[x].size() - 1; ~i; --i) { int y = a[x][i]; dep[y] = dep[x] + 1; dfs(y); root[x] = merge(root[x], root[y]); //我们要合并深度-数量线段树,可以直接通过简单的数值累加做合并 Root[x] = merge(Root[x], Root[y], x); //然后我们要通过颜色-深度线段树的查询,对深度-数量线段树做修订 } } void init() { node[ id = 0 ].v = node[0].l = node[0].r = 0; dep[1] = 1; dfs(1); } int L, R; int query(int o, int l, int r) { if (!o)return 0; if (l >= L&&r <= R)return node[o].v; int mid = (l + r) >> 1; int ans = 0; if (L <= mid)ans += query(node[o].l, l, mid); if (R > mid)ans += query(node[o].r, mid + 1, r); return ans; } int main() { scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i)scanf("%d", &c[i]), a[i].clear(); for (int i = 2; i <= n; ++i)scanf("%d", &x), a[x].push_back(i); init(); int pre = 0; for (int i = 1; i <= m; ++i) { scanf("%d%d", &x, &d); x ^= pre; d ^= pre; L = dep[x]; R = min(n, dep[x] + d); printf("%d\n", pre = query(root[x], 1, n)); } } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 65535/32768 K (Java/Others)
Output the answer in a new line for each test case.
解题思路:
【题意】
我们要找出最小的正整数n满足——
a*S(n)==b*S(2n)
a,b的范围都在[1,100]
【类型】
暴力
【分析】
这道题目的结果可能非常大,所以我们直接枚举n是要GG的。
首先可以有这样的基础性结论:
设gcd(a,b)=g, 我们可以先使得b=b/g, a=a/g
S(n):S(2n)==b:a,那么我们有S(n):S(2n)=b:a。
然后,一个好的做法是,我们研究本质问题。
我们发现,如果一个digit是0~4,那么*2的效益是完全获得的。
如果一个digit的是5~9,那么*2后会损失9的收益。
这里解释一下为什么会损失9的收益
对于digit是5~9的,*2之后会变成两位,即10+x,而计算S(n)的时候,10只能被记为1,故损失了9
a*S(n) == b*S(2n),
我们假设有l的长度是[0,4]范围,有L的长度是[5,9]范围
那么显然满足:
S(2n)=S(n)*2-L*9
替换一下——
a*S(n) == b*(2S(n)-L*9)
a*S(n) == 2b*S(n) -L*9*b
(2b-a)*S(n) == L*9*b
即——
9*b:2b-a = S(n):L
也就是说,我们得到了S(n)与L的比例关系。
然后模拟一遍即可。
怎么模拟呢?
我们不妨假设答案n仅有长度为L,且每一位都是5
然后得到了把数位和sum分撒出去。
对于sum余下的数值,我们依次加到尾巴上。
如果sum最后把长度为L的字串都填充为‘9‘之后,还有剩余,那么在前面贪心填充。
题目链接→HDU 5710 Digit-Sum
/*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<cmath> #include<complex> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 #define bitnum(a) __builtin_popcount(a) using namespace std; const int N = 100005; const int M = 120; const int inf = 1600000000; const int mod = 2009; int gcd(int a,int b) { if(a%b) return gcd(b,a%b); return b; } int ans[N]; int main() { int t,a,b,m,n,k,i; scanf("%d",&t); while(t--) { scanf("%d%d",&a,&b); m=2*b-a; n=9*b; if(m<0||5*m>n) puts("0"); else if(!m) puts("1"); else { k=gcd(m,n); m/=k; n/=k;n-=5*m; for(i=0;i<m;i++,n-=k) { k=min(4,n); ans[i]=5+k; } while(n>4) ans[m++]=4,n-=4; if(n) ans[m++]=n; for(i=m-1;i>=0;i--) printf("%d",ans[i]); puts(""); } } return 0; }
Accept: 0 Submit: 0
Time Limit: 2000/1000 MS (Java/Others) Memory Limit : 102400/65535 K (Java/Others)
For each test case, output the case number first, then the amount of maximum XM Brickgao can get.
解题思路:
【题意】
n(16)个点
m(n^2)条双向边
K(50)次hack机会
最远走L(2000)路程
每个点的初始点权为a[i](500)
每个点每hack一次点权下降b[i](不会变为负)
【类型】
DP(tsp)+贪心
【分析】
我们可以做状压DP
f[i][j]表示目前经过点的状态集合为i,当前所在点的位置为j的最小路径(算上返程到0点的路径)
这是典型的旅行商问题。
由此得出所有可以到达的合法点集,其状态用2进制表示为sta。
然后依次枚举所有的合法sta,
对于每一个合法sta的所有点权,用贪心原则,用优先队列选取最大的权值。
【时间复杂度&&优化】
O(2^17*17*17 + 2^17*K*log(17))
其实,17可以被优化为16。
然而为了减少出错还是用17的好。
题目链接→HDU 5711 Ingress
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> #include<assert.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; } const int N = 17, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f; int casenum, casei; int n, m, K, L; int x, y, z; int a[N], b[N]; int d[N][N]; void floyd() { for (int k = 0; k <= n; ++k) { for (int i = 0; i <= n; ++i) { for (int j = 0; j <= n; ++j) { gmin(d[i][j], d[i][k] + d[k][j]); } } } } int f[1 << 17][17]; bool ok[1 << 17]; void tsp() { int top = (1 << n + 1) - 1; MS(f, 63); MS(ok, 0); f[1][0] = 0; int ans = 0; for (int i = 0; i <= top; ++i) { for (int j = 0; j <= n; ++j)if (f[i][j] <= L) { if (f[i][j] + d[j][0] <= L)ok[i] = 1; for (int k = 1; k <= n; ++k) { gmin(f[i | 1 << k][k], f[i][j] + d[j][k]); } } if (ok[i]) { priority_queue< pair<int, int> >q; for (int j = 1; j <= n; ++j)if (i & 1 << j)q.push(MP(a[j], b[j])); if (q.empty())continue; int tmp = 0; for (int j = 1; j <= K; ++j) { tmp += q.top().first; q.push(MP(max(q.top().first - q.top().second, 0), q.top().second)); q.pop(); } gmax(ans, tmp); } } printf("Case %d: %d\n", casei, ans); } int main() { scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei) { scanf("%d%d%d%d", &n, &m, &K, &L); for (int i = 1; i <= n; ++i)scanf("%d", &a[i]); for (int i = 1; i <= n; ++i)scanf("%d", &b[i]); MS(d, 63); for (int i = 1; i <= m; ++i) { scanf("%d%d%d", &x, &y, &z); gmin(d[x][y], z); gmin(d[y][x], z); } floyd(); tsp(); } return 0; }菜鸟成长记
"巴卡斯杯" 中国大学生程序设计竞赛 - 女生专场(重现)解题思路
标签:
原文地址:http://blog.csdn.net/queuelovestack/article/details/51525913