Practice I
前言
已经被大佬们虐死了 T^T
接下来攻好长好长一段时间的Practice系列
刷题呢,个人感觉,没学算法那么痛苦,而且还快!
(这个系列题目不给截图)
Birthday Cake - UVA - 10167
题意
求一条直线Ax+By=0,将平面上的点等分
思路
今天训练的题目,个人能接受的算法真的是活久见!
看dalao用随机数的方法AC
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 200 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int a[N][2];
bool judge(int A, int B, int n){
int cnt1 = 0, cnt2 = 0;
for(int i = 0; i < n; i++){
if(A*a[i][0] + B*a[i][1] == 0) return false;
if(A*a[i][0] + B*a[i][1] < 0) cnt1++;
else cnt2++;
}
return cnt1 == cnt2;
}
int main(){
int n;
while(scanf("%d", &n) && n){
n <<= 1;
for(int i = 0; i < n; i++){
for(int j = 0; j < 2; j++){
scanf("%d", &a[i][j]);
}
}
int A, B;
while(true){
A = rand()%1001 - 500;
B = rand()%1001 - 500;
if(judge(A, B, n)) break;
}
printf("%d %d\n", A, B);
}
}
Zebras - CodeForces - 950C
题意
找出方法使得给定字符串能由若干个形如0,010,01010,…的子序列不重叠构成,输出序列总数,每条序列的大小和其上元素的相应位置,不能则输出-1
思路
填5个月前的坑
个人开了两个队列维护着做,看到0则看看能不能接上已有的1,不能则新建序列,看到1则看看接上已有的0,不能则无解
dalao的代码特别神仙!各位dalao可以百度这题题号看看dalao是怎么写的
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int N = 2e5 + 15;
const int inf = 0x3f3f3f3f;
char s[N];
vector<int> vec[N];
queue<int> quezero, queone;
int main(){
while(~scanf("%s", s)){
for(int i = 0; i < N; i++) vec[i].clear();
while(!quezero.empty()) quezero.pop();
while(!queone.empty()) queone.pop();
int tot = 0;
bool flag = true;
for(int i = 0; s[i]; i++){
if(s[i] == '0'){
if(!queone.empty()){
int idx = queone.front();
queone.pop();
vec[idx].push_back(i + 1);
quezero.push(idx);
}else{
vec[++tot].push_back(i + 1);
quezero.push(tot);
}
}else{
if(quezero.empty()){
flag = false;
break;
}
int idx = quezero.front();
quezero.pop();
vec[idx].push_back(i + 1);
queone.push(idx);
}
}
if(!queone.empty()){
flag = false;
}
if(flag){
printf("%d\n", tot);
for(int i = 1; i <= tot; i++){
printf("%d", vec[i].size());
for(int j = 0; j < vec[i].size(); j++){
printf(" %d", vec[i][j]);
}
puts("");
}
}else{
puts("-1");
}
}
return 0;
}
Mister B and PR Shifts - CodeForces - 820D
题意
输出序列循环同构中 | p[i] - i | 的最小值,以及循环次数
思路
看了dalao的题解才懂了(大概七成多把,懵懵懂懂),具体看代码注释
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e6 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int a[N], cnt[N];
int main(){
int n;
while(~scanf("%d", &n)){
//cnt[i]:位移i次到达本身的数的数量,到达此点后再位移会由sub变为add(含义见下句)
memset(cnt, 0, sizeof(cnt));
int add = 0, sub = 0; //add:下次位移会增加1的数量,sub:反之
ll sum = 0; //当前和
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
sum += (int)fabs(a[i] - i);
if(i < a[i]){
cnt[a[i] - i]++;
sub++;
}else{
cnt[(a[i] - i + n)%n]++;
add++;
}
}
ll anssum = sum;
int ansidx = 0;
for(int i = 1; i <= n; i++){
// + add - sub - 1:其中-1是因为最后一位要调到第一位,但是其中add把它算多了一次
// 因为最后一位下标为n,而a[i] <= n,因此算多的部分必定给了add
sum = sum + add - sub - 1 - (n - a[n - i + 1]) + (a[n - i + 1] - 1);
// 分两种情况:
// ① a[n - i + 1] != 1,那么调到头由add变为sub,所以 add - 1, sub + 1
// ② a[n - i + 1] == 1,那么掉到头由add变为add,不变
// 但是呢,调过去后a[1] == 1,因此算在了cnt[i]中
// 所以 +(cnt[i] - 1) , -(cnt[i] - 1)
// 化简后相同
add = add + cnt[i] - 1;
sub = sub - cnt[i] + 1;
if(anssum > sum){
anssum = sum;
ansidx = i;
}
}
printf("%lld %d\n", anssum, ansidx);
}
return 0;
}
Games on a CD - CodeForces - 727E
题意
顺时针给出一环形字符串,再给定g个长度为k的字符串,问环形字符串能否从由g个字符串中的某一些不重叠组成,能则从任意位置开始顺时针输出字符串的位置,不能输出NO
思路
本题似乎卡Hash,dalao们好多都用双Hash做的,蒟蒻不会 _(:з」∠)_
用Hash结果WA了10发,最后用AC自动机过的,官方题解给用的是后缀树
将g个字符串加入AC自动机中,然后用环形字符串跑AC自动机,匹配得到的位置就更新一下f数组(其中存的是g个字符串各自所在的位置),最后穷举f数组,如果其中答案都不重复则YES,否则NO
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 2e6 + 15;
const int inf = 0x3f3f3f3f;
typedef long long ll;
bool used[N];
char s[N], p[N];
int nxt[N][26];
int fail[N];
int pos[N];
int f[N];
int tot;
inline void init(){
memset(nxt, -1, sizeof(nxt));
memset(fail, 0, sizeof(fail));
memset(pos, -1, sizeof(pos));
tot = 1;
}
void push(int pp, char* p){
int rt = 0;
for(int i = 0; p[i]; i++){
int idx = p[i] - 'a';
if(nxt[rt][idx] == -1){
nxt[rt][idx] = tot++;
}
rt = nxt[rt][idx];
}
pos[rt] = pp;
}
int que[N], head, tail;
void setFail(){
for(int j = 0; j < 26; j++){
if(nxt[0][j] == -1) nxt[0][j] = 0;
else que[tail++] = nxt[0][j];
}
while(head != tail){
int rt = que[head++];
for(int j = 0; j < 26; j++){
if(nxt[rt][j] != -1){
fail[nxt[rt][j]] = nxt[fail[rt]][j];
que[tail++] = nxt[rt][j];
}else{
nxt[rt][j] = nxt[fail[rt]][j];
}
}
}
}
void query(char* s, int k){
int rt = 0;
for(int i = 0; s[i]; i++){
int idx = s[i] - 'a';
rt = nxt[rt][idx];
f[i - k + 1] = pos[rt];
}
}
int main(){
int n, plen;
while(~scanf("%d%d", &n, &plen)){
init();
int slen = n*plen;
scanf("%s", s);
for(int i = 0; i < slen; i++){
s[i + slen] = s[i];
}
slen <<= 1;
int k;
scanf("%d", &k);
for(int i = 1; i <= k; i++){
scanf("%s", p);
push(i, p);
}
setFail();
query(s, plen);
int ansp = -1;
for(int i = 0; i < plen; i++){
int j, q;
for(j = i, q = 0; q < n; j += plen, q++){
if(f[j] == -1 || used[f[j]]) break;
used[f[j]] = true;
}
if(q == n){
ansp = i;
break;
}
for(; j >= i; j -= plen) used[f[j]] = false;
}
if(ansp == -1) puts("NO");
else{
puts("YES");
for(int i = ansp, q = 0; q < n; i += plen, q++){
printf("%d", f[i]);
if(q < n - 1) putchar(' ');
}
puts("");
}
}
return 0;
}
Replacement - CodeForces - 570C
题意
不断更新给定字符串中给定位置的字符,询问每次操作后把相邻”..”合并为一个”.”所需的次数
思路
初始答案是 连成一块的”.”的数量 - 1 的和
每次更新时,若替代的字符和被替代的字符不都是”.”或不都是字母,则找找它的左一位和右一位(若有),若有则对应+1(-1)
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 3e5 + 15;
char s[N];
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
s[0] = s[n + 1] = 'a';
scanf("%s", s + 1);
int ans = 0;
for(int i = 2; i <= n; i++){
if(s[i] == '.' && s[i - 1] == '.'){
ans++;
}
}
while(m--){
int pos;
char ch;
scanf("%d %c", &pos, &ch);
if(s[pos] == '.' && ch != '.'){
if(s[pos - 1] == '.') ans--;
if(s[pos + 1] == '.') ans--;
}else if(s[pos] != '.' && ch == '.'){
if(s[pos - 1] == '.') ans++;
if(s[pos + 1] == '.') ans++;
}
s[pos] = ch;
printf("%d\n", ans);
}
}
return 0;
}
String Reconstruction - CodeForces - 828C
题意
要求构造一个字符串,根据给定的一些字符串和其所在位置,构造出字典序最小的字符串
思路
首先开数组标记字符串所在原输入的位置,若由冲突则保留长度最大的
然后跑一遍得到字符串长度最大值
根据这一最大值,构造字符串,构造过程中,遇到有标记则不断尝试向右延伸,即比较当前所能达到的右边界和该标记所能达到的右边界值,若能向右延伸则切换到该标记所在的字符串,若遇到无标记且当前串被填写完了,则填写字母’a’(满足字典序最小),直到到达长度最大值
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6 + 15;
int mp[N];
int maxlen[N];
char ans[N];
string s[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int m;
while(cin >> m){
memset(maxlen, 0, sizeof(maxlen));
memset(mp, -1, sizeof(mp));
memset(ans, 0, sizeof(ans));
int endpos = 0;
for(int i = 1; i <= m; i++){
cin >> s[i];
int slen = s[i].size();
int k;
cin >> k;
while(k--){
int tmp;
cin >> tmp;
if(maxlen[tmp] < slen){
maxlen[tmp] = slen;
mp[tmp] = i;
}
endpos = max(endpos, tmp + maxlen[tmp] - 1);
}
}
int r = 1;
int cur = mp[1];
int j = 0;
for(int i = 1; i <= endpos; i++){
if(mp[i] != -1 && r < i + s[mp[i]].size()){
r = i + s[mp[i]].size();
cur = mp[i];
j = 0;
}
if(j < s[cur].size()){
ans[i] = s[cur][j];
j++;
}
}
r--;
//cout << "endpos:" << endpos << endl;
for(int i = 1; i <= endpos; i++){
if(ans[i] == 0) cout << 'a';
else cout << ans[i];
}
cout << endl;
}
return 0;
}