前言:三道大水题。。。
纸牌
题目描述
纸牌选手wyz喜欢玩纸牌。
wyz有2n张纸牌,点数分别为1到2n。wyz要和你玩一个游戏,这个游戏中,每个人都会分到n张卡牌。游戏一共分为n轮,每轮你们都要出一张牌,点数大者获胜。
不自量力的wyz觉得你很菜,于是每轮他都会先于你出牌,你可以根据他出的牌来做决策。
游戏开始了,你拿到了你的牌,你现在想知道,你最多能够获胜几轮?
对于32.5%的数据,保证1<=n<=100
对于100%的数据,保证1<=n<=50,000
保证数据的合法性,即你即不会拿到重复的牌,又不会拿到超出点数范围的牌。
输入格式
第一行1个正整数n。
第2行到第n+1行每行一个正整数a[i],表示你的第i张牌的点数。
输出格式
一行一个整数表示你最多能够获胜的轮数。
输入样例
2
1
4
输出样例
1
题解(贪心+单调)
这题就是什么塞翁失马,应该是田忌赛马的加强版。
我们将手牌按小到大排序,然后用自己的牌去吃对方的牌,由于单调递增,所以时间 O(n) 。
代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
int n, x, ans;
int a[N], b[N];
bool vis[N];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &x);
vis[x] = true;
}
for(int i = 1; i <= 2*n; i++){
if(vis[i]) a[++a[0]] = i;
else b[++b[0]] = i;
}
for(int i = 1, j = 1; i <= n; i++){
for(; j <= n; j++) if(a[j] > b[i]){
ans ++;
j ++;
break;
}
}
printf("%d\n", ans);
return 0;
}
杯具
题目描述
杯具选手wyz喜欢玩杯具。
wyz有2个容量分别为n单位、m单位的没有刻度的杯具。wyz有t分钟可以摆弄他的杯具。每一分钟,他都可以做下面4件事中的任意一件:
(1)用水龙头放满一个杯具。
(2)倒空一个杯具。
(3)把一个杯具里的水倒到另一个杯具里,直到一个杯具空了或者另一个杯具满了。(看哪种情况先发生)
(4)什么都不做。
wyz希望最后能获得d个单位的水,假设最后两个杯具中水量的总和为x,那么他的不开心度就为|d-x|。
现在你想知道,wyz的不开心度最小是多少。
对于10%的数据,保证t=1
对于20%的数据,保证t<=2
对于40%的数据,保证t<=4
对于100%的数据,保证1<=n,m<=100,1<=t<=100,1<=d<=200
输入格式
第一行4个整数n、m、t、d,分别表示两个杯具的容量、时间限制以及期望值。
输出格式
一行一个整数表示wyz的最小不开心度。
输入样例
7 25 2 16
输出样例
9
题解(dp/bfs)
我都不想说这题了,话说这不是普及组难度吗?一个很简单的dp,以时间为阶段,记录两个杯子分别的水量是否可以到达, f[i][j][k]=true/false 。然后直接推,最后找答案。
或者直接无脑宽搜,哪种都好, n <script type="math/tex" id="MathJax-Element-81">n</script>才100,时间完全不是问题。
然而我考试时闲得无聊,两种方法都写了,还拿来拍了半天。。。
代码(dp)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iostream>
#define N 111
#define oo 0x7fffffff
using namespace std;
int n, m, T, D, ans = oo;
bool f[N][N][N];
int main(){
scanf("%d%d%d%d", &n, &m, &T, &D);
f[0][0][0] = true;
for(int i = 0; i < T; i++)
for(int c1 = 0; c1 <= n; c1++)
for(int c2 = 0; c2 <= m; c2++){
if(!f[i][c1][c2]) continue;
f[i+1][c1][c2] = true;
f[i+1][0][c2] = true;
f[i+1][c1][0] = true;
f[i+1][n][c2] = true;
f[i+1][c1][m] = true;
if(n - c1 >= c2)
f[i+1][c1+c2][0] = true;
else
f[i+1][n][c2-n+c1] = true;
if(m - c2 >= c1)
f[i+1][0][c1+c2] = true;
else
f[i+1][c1-m+c2][m] = true;
}
for(int c1 = 0; c1 <= n; c1++)
for(int c2 = 0; c2 <= m; c2++){
if(!f[T][c1][c2]) continue;
ans = min(ans, abs(c1+c2-D));
}
printf("%d\n", ans);
return 0;
}
代码(bfs)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iostream>
#define N 111
#define oo 0x7fffffff
using namespace std;
int n, m, T, D, ans = oo;
bool f[N][N][N];
struct Data{
int t, c1, c2;
Data() {}
Data(int _t, int _c1, int _c2):t(_t), c1(_c1), c2(_c2) {}
}q[N*N*N];
int head, tail;
int main(){
scanf("%d%d%d%d", &n, &m, &T, &D);
q[0] = Data(0, 0, 0);
f[0][0][0] = true;
while(head <= tail){
int i = q[head].t, c1 = q[head].c1, c2 = q[head].c2;
head ++;
if(i == T) continue;
if(!f[i+1][c1][c2]){
q[++tail] = Data(i+1, c1, c2);
f[i+1][c1][c2] = true;
}
if(!f[i+1][0][c2]){
q[++tail] = Data(i+1, 0, c2);
f[i+1][0][c2] = true;
}
if(!f[i+1][c1][0]){
q[++tail] = Data(i+1, c1, 0);
f[i+1][c1][0] = true;
}
if(!f[i+1][n][c2]){
q[++tail] = Data(i+1, n, c2);
f[i+1][n][c2] = true;
}
if(!f[i+1][c1][m]){
q[++tail] = Data(i+1, c1, m);
f[i+1][c1][m] = true;
}
if(n - c1 >= c2){
if(!f[i+1][c1+c2][0]){
q[++tail] = Data(i+1, c1+c2, 0);
f[i+1][c1+c2][0] = true;
}
}
else{
if(!f[i+1][n][c2-n+c1]){
q[++tail] = Data(i+1, n, c2-n+c1);
f[i+1][n][c2-n+c1] = true;
}
}
if(m - c2 >= c1){
if(!f[i+1][0][c1+c2]){
q[++tail] = Data(i+1, 0, c1+c2);
f[i+1][0][c1+c2] = true;
}
}
else{
if(!f[i+1][c1-m+c2][m]){
q[++tail] = Data(i+1, c1-m+c2, m);
f[i+1][c1-m+c2][m] = true;
}
}
}
for(int c1 = 0; c1 <= n; c1++)
for(int c2 = 0; c2 <= m; c2++){
if(!f[T][c1][c2]) continue;
ans = min(ans, abs(c1+c2-D));
}
printf("%d\n", ans);
return 0;
}
辣鸡
题目描述
wyz在后院养了许多辣鸡。wyz的后院可以看成一个A*B的矩形,左下角的坐标为(0,0),右上角的坐标为(A,B)。
wyz还在后院里建了许多栅栏。有n个平行于y轴的栅栏a1..an,表示挡在(ai,0)到(ai,B)之间。有m个平行于x轴的栅栏b1..bn,表示挡在(0,bi)到(A,bi)之间。这样,平面被划成了(n+1)*(m+1)块辣鸡的活动区域。
为了方便辣鸡的活动,wyz现在要去掉某些栅栏的一部分,使得每一块活动区域都连通。
同时,每次修改栅栏只能去掉从某个交点到另一个交点的一整段栅栏。举(打)个比(栗)方(子):
原来是这样的布局,经过修改可以变成这样:
现在,wyz想知道,要使得每一块辣鸡活动区域都联通,最少需要去掉多少长度的栅栏。
对于10%的数据,A,B<=1000,n,m<=20
对于30%的数据,A,B<=1000,000,n*m<=25,000
对于40%的数据,n*m<=250,000
对于50%的数据,n*m<=4,000,000
对于100%的数据,1<=A,B<=1000,000,000,1<=n,m<=25,000
数据保证 0 < a[i] < A,0 < b[i] < B
输入格式
第一行4个正整数A、B、n、m,描述了后院的大小和两种栅栏的数目。
第2行到第n+1行,每行1个正整数,第i+1行的数描述了a[i]。
第n+2行到第n+m+1行,每行1个正整数,第n+i+1行的数描述了b[i]。
输出格式
一行一个整数表示需要去掉的栅栏的最小长度总和。
输入样例
15 15 5 2
2
5
10
6
4
11
3
输出样例
44
题解(MST+找规律)
这是题原题。。。
题目很明显是在求最小生成树。
转化就像下图一样:
原图中的横着的栅栏就是竖着的边,竖着的栅栏就是横着的边,每一横排、竖排的边权一样。破坏栅栏就是在连边。如果直接做Kruskal肯定超时,然而第一次做这题的时候,脑壳坏了,想不到该怎么搞,结果就直接来了。甚至我想到了排序后必然是一段一段的,但是判环还是没有想到点上。
那时随便在草稿上画了一下,发现一开始的情况和后来不太一样,觉得没什么规律可循。其实就是有规律可循啊。。。
我们发现每次必然加整一行或列,出现环就会少加一部分,行少加是受到列的影响,列也一样。行一开始整行加,后来有了两列一行后,再整行加就会有环,只能少加一个,后面随着列的增加,行越加越少,每次少一,这就发现了规律。列也是一样的(画个图比较好理解)。
于是直接将读入转成边排序,然后按着规律加边算答案。
再见,超时。
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#define N 25010
using namespace std;
typedef long long LL;
LL A, B, a[N], b[N], ans;
int n, m, Rcnt, Lcnt;
struct Data{
LL len, id;
bool operator < (const Data& Q)const {return len < Q.len;}
}fen[N<<1];
int main(){
scanf("%lld%lld%d%d", &A, &B, &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int i = 1; i <= m; i++) scanf("%lld", &b[i]);
a[0] = 0; a[n+1] = A;
b[0] = 0; b[m+1] = B;
sort(a, a+n+2);
sort(b, b+m+2);
for(int i = 1; i <= n+1; i++) fen[i].len = a[i] - a[i-1], fen[i].id = 0;
for(int i = 1; i <= m+1; i++) fen[i+n+1].len = b[i] - b[i-1], fen[i+n+1].id = 1;
sort(fen+1, fen+n+m+3);
for(int i = 1; i <= n + m + 2; i++){
if(!fen[i].id){
if(Rcnt >= 2 && Lcnt >= 1) ans += fen[i].len * (m - Rcnt + 1);
else ans += fen[i].len * m;
Lcnt ++;
}
else{
if(Lcnt >= 2 && Rcnt >= 1) ans += fen[i].len * (n - Lcnt + 1);
else ans += fen[i].len * n;
Rcnt ++;
}
}
printf("%lld\n", ans);
return 0;
}
总结
这次比赛太水了,有十几个人AK,只有蒟蒻我还在水总结。
黑暗。