SDNU_ACM_ICPC_2021_Winter_Practice_8th [个人赛]
G - Coin Game HDU - 3951(博弈)
题意:
给你两个数n,k,把1 ~ n这n个数按顺序排在一个圆环上(首位相接),每次每个人可以拿1 ~ k个数,这k个数必须是连续的。
分析:
如果k = 1,则判断n的奇偶性,奇数第一个人赢,偶数第二个人赢。
若k > 1,如果一次能拿完,则第一个人赢,如果一次拿不完,则第二个人赢。
因为,第一个人第一次拿完后,第二个人选择拿第一个人拿的数对面的数字,并且使整个圆环就被分成了相等两半,之后第一个人每次拿的时候都一定会从这两半中的一半拿,而第二个人只需要从另一半中拿相同的即可。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int t, n, k;
int main()
{
cin >> t;
for(int i = 1; i <= t; ++i)
{
cin >> n >> k;
cout << "Case "<< i << ": ";
if(k == 1)
{
if(n & 1) cout << "first\n";
else cout << "second\n";
}
else
{
if(n <= k) cout << "first\n";
else cout << "second\n";
}
}
return 0;
}
C - Genius’s Gambit CodeForces - 1492D (构造)
题意:
给你a, b, k三个数,让你找两个数x,y(x >=y)且x,y的二进制都是由a个0和b个1组成,并且x-y得到的数的二进制有k个1,x,y的前缀0不算数。
分析:
因为x和y中包含的0和1相同多,把这两个01串从后向前看,第一位不同处相减一定是1,而它的前一位受他的影响如果相同则是1,不同则是0。所以这两个数相减,构成的含1最多的数应该是
x:11xxxxxx0
y:10xxxxx1
xxxxx处x和y相同,如想减少1的个数只需将x最后的0和y最后的1同时向前移动即可。
可以看出最多出现a + b - 2个1,但是这对x和y有要求,即a必须大于0,b必须大于1,数据范围中a和b是可以取到0和1的需要特别考虑。
还有许多地方好麻烦。。。
AC代码:
代码是按照下面的提纲写的,在判断k-1个相同数字时输出0还是1的地方写的过于复杂,应该可以优化的。
#include <bits/stdc++.h>
using namespace std;
int a, b, k;
int main()
{
cin >> a >> b >> k;
if(a == 0)
{
if(k == 0)
{
cout << "Yes\n";
for(int i = 1; i <= b; ++i) cout << 1;
putchar('\n');
for(int i = 1; i <= b; ++i) cout << 1;
putchar('\n');
}
else cout << "No\n";
return 0;
}
if(b == 1)
{
if(k == 0)
{
cout << "Yes\n";
cout << 1 ;
for(int i = 1; i <= a; ++i) cout << 0 ;
putchar('\n');
cout << 1 ;
for(int i = 1; i <= a; ++i) cout << 0 ;
putchar('\n');
}
else cout << "No\n";
return 0;
}
if(k == 0)
{
cout << "Yes\n";
for(int i = 1; i <= b; ++i) cout << 1 ;
for(int i = 1; i <= a; ++i) cout << 0;
putchar('\n');
for(int i = 1; i <= b; ++i) cout << 1 ;
for(int i = 1; i <= a; ++i) cout << 0;
putchar('\n');
return 0;
}
if(k <= a + b - 2)
{
cout << "Yes\n";
cout << 11 ;
if(min(b - 2, a - 1) >= k - 1)
{
for(int i = 1; i <= k - 1; ++i) cout << 1;
cout << 0;
b = b - 2 - k + 1;
--a;
for(int i = 1; i <= b; ++i) cout << 1;
for(int i = 1; i <= a; ++i) cout << 0;
putchar('\n');
cout << 10 ;
for(int i = 1; i <= k - 1; ++i) cout << 1;
cout << 1;
for(int i = 1; i <= b; ++i) cout << 1;
for(int i = 1; i <= a; ++i) cout << 0;
putchar('\n');
}
else
{
if(b - 2 <= k - 1)
{
for(int i = 1; i <= b - 2; ++i) cout << 1 ;
for(int i = 1; i <= k - b + 1; ++i) cout << 0;
cout << 0 ;
for(int i = 1; i <= a + b - k - 2; ++i) cout << 0 ;
putchar('\n');
cout << 10 ;
for(int i = 1; i <= b - 2; ++i) cout << 1 ;
for(int i = 1; i <= k - b + 1; ++i) cout << 0;
cout << 1 ;
for(int i = 1; i <= a + b - k - 2; ++i) cout << 0;
putchar('\n');
}
else
{
for(int i = 1; i <= a - 1; ++i) cout << 0 ;
for(int i = 1; i <= k - a; ++i) cout << 1 ;
cout << 0 ;
for(int i = 1; i <= a + b - k - 2; ++i) cout << 1;
putchar('\n');
cout << 10 ;
for(int i = 1; i <= a - 1; ++i) cout << 0 ;
for(int i = 1; i <= k - a; ++i) cout << 1 ;
cout << 1 ;
for(int i = 1; i <= a + b - k - 2; ++i) cout << 1;
putchar('\n');
}
}
}
else cout << "No\n";
return 0;
}
B - Maximum width CodeForces - 1492C(双指针 + 思维)
题意:
给你两个数m和n代表字符串s和t的长度,从s的子序列(可以不连续)中找t,求这个不连续的子序列跨度最大是多少。
分析:
对于t中的每一个字符,找到它在s中最前面和最后面出现的位置,之后做差取max。
这需要对字符串s先从前遍历一遍,同时对t从前遍历,利用两个指针以保证找到的位置即时最靠前的有能保证这个点之前的子序列中有t中前面的元素。
然后对s和t利用双指针从后向前。
最后遍历一遍t
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n, m, last, ans;
string s, t;
int qian[200050];
int hou[200050];
int main()
{
cin >> n >> m;
cin >> s >> t;
last = -1;
//这个while循环移动指针很巧妙
for(int i = 0; i < m; ++i)
{
while(s[++last] != t[i])
{
//do something...
}
qian[i] = last;
}
last = n;
for(int i = m - 1; i >= 0; --i)
{
while(s[--last] != t[i])
{
//do something...
}
hou[i] = last;
}
for(int i = 1; i < m; ++i)
{
ans = max(ans, hou[i] - qian[i - 1]);
}
cout << ans << '\n';
return 0;
}
F - Monthly Expense POJ - 3273(二分答案 + 检验)
可恶啊,这个题我竟然没做。。。
题意:
两个数n,m,有n天,每天花一些钱,需要把这n天分成m组(一组中天数必须是连续的),求这m组中最小的组最大花销。
分析:
最小的最大,很有可能是个二分, ~~~ 果然
先找到左右界,左界即最大的那一天(即假设一天一个组),右界即n天花销之和(一共一个组)。
开始二分x
对于x判断可以分成几个组group,如果group大于m说明x太小了(分的组多了),如果group小于m说明x太大了,如果正好等于也不一定对,总之就是最后一次mid。
注意:判断组数时,初始化为1,如果不的话,最后一组会被漏掉
AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int n, m;
ll ar[100050];
ll l, r, mid, ans;
bool judge(ll x)
{
int sum = 0;
int group = 1;//初始化为1
for(int i = 1; i <= n; ++i)
{
if(sum + ar[i] <= x) sum += ar[i];
else
{
sum = ar[i];
group++;
}
}
if(group > m) return false;
else return true;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
{
scanf("%lld", &ar[i]);
r += ar[i];
l = max(l, ar[i]);
}
//要写等于的,不写的话样例输出了一个501 ????
while(l <= r)
{
mid = (l + r) >> 1;
if(!judge(mid)) l = mid + 1;
else
{
r = mid - 1;
ans = mid;
}
}
cout << ans << '\n';
return 0;
}
M - Card Deck CodeForces - 1492B
题意:
先输入一个t,代表样例个数,然后输入一个n,代表有n个数,再把这n个数输进来。每次可以从这个数组取后k个数(k不固定),取出来的数构成新数列,先取的那一组数放在新数组的前面,每次取的一组内数字相对位置不可以调换,对于每个新数组都有一个
输出这个值最大的那个数组,如果有多个输出任意一个即可。
分析:
这个题就是大胆的猜想~
贪心的每次拿最大的数的后面的一坨数,因为前面乘了一个n^(n-i),所以让大的数尽可能靠前。
我的做法是定义结构体数组和普通数组,结构体需要存数值以及这个数再原数组的位置。然后结构体排序,从大到小排,然后遍历结构体,获得当前数字再原数组的位置,然后输出这个位置到还剩下的最后(语无伦次~)也就是说需要两个量来辅助,第一个量是当前要输出的一坨数在原数组的左界,这个数等于当前结构体数组中寸的位置,第二个量是原数组还剩的数字中最后一个的下一个位置,这个量需要一直取min获得
然后 ~~~ 自己看了看数据范围以为会超时,但竟然奇迹般地AC了。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int t, n;
int ar[100050];
struct node
{
int a, pos;
}br[100050];
int l, r, mxno;
bool cmp(struct node x, struct node y)
{
return x.a > y.a;
}
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &ar[i]);
br[i].a = ar[i];
br[i].pos = i;
}
sort(br + 1, br + n + 1, cmp);
r = n + 1;
for(int i = 1; i <= n; ++i)
{
mxno = br[i].pos;
for(int i = mxno; i < r; ++i) printf("%d ", ar[i]);
r = min(mxno, r);
}
putchar('\n');
}
return 0;
}
然后~~~
学了一个更简单的办法。要好好利用数据特征 1~n的全排列
一开始想法的是最开始肯定拿数字n及其后面的数组,然后再剩下的数字中找最大的再拿这个数字后面的,但这个找最大数字确实比较麻烦,然后想过通过上次拿的数字来确定当前最大的数字,发现好像也不是很简单。
然后,学到了这个我们从后向前找最大数字(一开始想过这个办法但是是想从后向前遍历原数组),我们应该遍历n到1这n个数,是这样一个从后向前,判断每个数是否在剩下的区间内即可,这个需要一个数组来保存每个数的位置,以及现在数组的长度。
#include <bits/stdc++.h>
using namespace std;
int t, n;
int pre;//当前数组长度 + 1
int ar[100050];//原数组
int pos[100050];//pos[i]的值是数字i在原数组的下标
struct node
{
int l, r;//这个是左界和右界(左闭右开),要输出的那个区间~
};
vector<struct node> ans;//存每个要被输出的区间
int main()
{
scanf("%d", &t);
while(t--)
{
//多组输入记得清空
ans.clear();
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &ar[i]);
pos[ar[i]] = i;//存位置
}
pre = n + 1;
//遍历n~1这n个数,判断它们是不是还在区间内
for(int i = n; i > 0; --i)
{
//如果这个数在区间内,那么把这个数的位置作为某次输出的左界,区间长度作为右界,并且更新区间长度
if(pos[i] < pre)
{
ans.push_back({pos[i], pre});
pre = pos[i];
}
}
//根据上面的操作,我们需要取ans.size()次,每次取的位置都在vector里了
for(int i = 0; i < ans.size(); ++i)
{
for(int j = ans[i].l; j < ans[i].r; ++j)
{
printf("%d ", ar[j]);
}
}
putchar('\n');
}
return 0;
}
I - 最长回文 HDU - 3068
manacher算法
要用这个算法,交了好几发都超时了,最后把输入cin改成scanf过了
#include <bits/stdc++.h>
using namespace std;
char s[110050];
int index, mx, len;
int r, c;
int par[220050];
char ch[220050];
int main()
{
while(~scanf("%s", s))
{
if(strlen(s) == 0)
{
printf("0\n\n");
continue;
}
len = strlen(s) * 2 + 1;
index = 0;
for(int i = 0; i < len; ++i)
{
if(i & 1) ch[i] = s[index++];
else ch[i] = '#';
}
r = -1;
c = -1;
mx = 0;
for(int i = 0; i < len; ++i)
{
if(r > i) par[i] = min(par[2 * c - i], r - i);
else par[i] = 1;
while(i + par[i] < len && i - par[i] > -1)
{
if(ch[i + par[i]] == ch[i - par[i]]) ++par[i];
else break;
}
if(i + par[i] > r)
{
r = i + par[i];
c = i;
}
mx = max(mx, par[i]);
}
printf("%d\n", mx - 1);
}
return 0;
}