ECNU XCPC 2021 November Training #1
ECNU XCPC 2021 November Training #1
难度乱序然后上来心态崩了。好几道题的解法挺巧妙的,但是考场上就是没写出来。D题还写挂了……白给
A string
problem
给定空字符串\(S\),执行以下\(Q\)次操作:
每次操作形式:
\(0\) \(c\) 表示在\(S\)之前加字符\(c\);
\(1\) \(c\) 表示在\(S\)之后加字符\(c\);
\(2\) \(t\) 询问\(t\)是否是\(S\)的一个带余周期,(设当前\(S\)长度为\(len\),对任意\(0<=i<=len-t-1\), 都满足\(s_i == s_{i+t}\))
\(3\) 询问\(S\)是否是回文串;
sov
force
每次修改,维护这个序列的正序哈希和倒序哈希,用于判断回文。
同时维护最长的周期值\(len\),如果\(t\)能被\(len\)整除,那么周期存在。否则暴力查看。
最长用时\(9.77s\)卡着时限过掉了。
std
O(nlogn):线段树维护hash,询问周期t时比较s[1..n-t]和s[t+1..n]哈希值是否相同。
O(n)因为哈希可以看成对一个base进制数取模,所以可以把加在前面的字符和加在后面的字符分别看成小数点前面的部分和后面的部分,预处理base的正次幂和负次幂(正次幂的逆元)就好了 ——Richard
code
#include<bits/stdc++.h>
using namespace std;
const int base = 13331;
const int MAXN = 1e6 + 10;
unsigned long long hash1 = 0, hash2 = 0, power = 1;
vector<bool> opt;
int q;
char s[MAXN << 2];
int l = MAXN << 1, r = (MAXN << 1) - 1;
int n, len;
int main()
{
scanf("%d", &q);
opt.push_back(1);
opt.push_back(1);
len = 1;
while(q--)
{
int p, p1, p2;
char c;
scanf("%d", &p1);
p2 = (opt[opt.size() - 2] << 1) | (opt[opt.size() - 1]);
p = p1 ^ p2;
if(p == 0)
{
++n;
scanf(" %c", &c);
s[--l] = c;
hash1 = hash1 + c * power;
hash2 = hash2 * base + c;
power *= base;
if(s[l] == s[l + len])
continue;
if(s[l] == s[r] && l != r)
len = n - 1;
else
len = n;
}
else if(p == 1)
{
++n;
scanf(" %c", &c);
s[++r] = c;
hash1 = hash1 * base + c;
hash2 = hash2 + power * c;
power *= base;
if(s[r] == s[r - len])
continue;
if(s[l] == s[r] && l != r)
len = n - 1;
else
len = n;
}
else if(p == 2)
{
int t;
scanf("%d", &t);
if(t % len == 0)
{
puts("Yes");
opt.push_back(1);
continue;
}
register bool ans = 1;
for(register int i = l; i + t <= r; ++i)
{
if(s[i] != s[i + t])
{
ans = false;
break;
}
}
puts(ans ? "Yes" : "No");
opt.push_back(ans);
}
else if(p == 3)
{
puts(hash1 == hash2 ? "Yes" : "No");
opt.push_back(hash1 == hash2);
}
}
}
B 二人游戏
problem
SY和JJ两个玩游戏。现有集合\(S = \{1,…,n*m+1\}\),两人轮流从\(S\)中取出一个数放到序列\(a\)的尾部,序列\(a\)初始为空.
\(P(a)\)表示此时\(a\)中含有长度为\(n + 1\)的递增子序列,或者长度为\(m + 1\)的递减子序列.
这个游戏有两个版本:
版本\(1\):谁首次满足\(P(a)\)谁获胜
版本\(2\):谁首次满足\(P(a)\)谁失败
无论什么版本,一旦某方无法取数,谁失败.
给定\(n\),\(m\)和游戏版本.SY和JJ都非常聪明.SY先手,请你告诉他,他是否能赢.
版本为\(1\)时:\(n\in[0,2], m\in[0,10^{18}]\)
版本为\(2\)时:\(n\in[0,1], m\in[0,10^{18}]\)
sov
经典的博弈论,注意到n只有 0,1, 2,分情况讨论即可。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
int t, n, m, pos;
cin >> t;
while(t--)
{
cin >> n >> m >> pos;
if(pos == 1)
{
//chose any one, he will win;
if(n == 0 || m == 0)
puts("YES");
else if(n == 1)
if((m + 1) % 2 == 1)
puts("YES");
else
puts("NO");
else if(n == 2)
puts("YES");
}
else if(pos == 2)
{
//chose any one,he wiil false
if(n == 0 || m == 0)
puts("NO");
//choose the min
else if(n == 1)
puts("YES");
}
}
}
C Tournament
problem
给定一张有向图,\(i,j\)之间有且仅有一条边。每次操作可以使一条边反向,问最少需要几次操作可以使这张图没有三元环。\((n <= 10)\)
sov
枚举所有的连边方式即可。具体实现由一个数组枚举全排列
code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 15;
int n;
bool mp[MAXN][MAXN];
int a[MAXN];
int ans = INT_MAX, cnt = 0;
int main()
{
cin >> n;
for(register int i = 1; i <= n; ++i)
{
register long long x;
cin >> x;
for(register int j = n; j >= 1; --j)
{
mp[i][j] = x % 10;
x /= 10;
}
}
for(register int i = 1; i <= n; ++i)
a[i] = i;
do
{
cnt = 0;
for(register int i = 1; i <= n; ++i)
{
for(register int j = i + 1; j <= n; ++j)
{
cnt += !mp[a[i]][a[j]];
}
}
ans = min(cnt, ans);
}while(next_permutation(a + 1, a + 1 + n));
cout << ans << endl;
}
D 开普勒表示
problem
定义排列\(\{a_i\}的开普勒表示\):连接有向边\(i\)->\(a_i\),这时图中会出现环(包括自环),将每一个环内最大的数转到开头,以环内最大值为关键值排序所有的环,并按顺序输出。
现在给一个排列,求开普勒表示;或给开普勒表示,求原数列(没有输出-1)
sov
求开普勒表示直接建图跑一遍就好,反正都是\(O(n)\)
推原数列则是逆向,需要找到每个环,这个环中前一个数就是后一个数的位置。满足环的开头的条件为:这个数大于前一个数,且大于当前环中的最大值。
如何-1:发现这个数的位置被占了
code
考场代码有点乱就这样吧
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 4e6 + 5;
int op, n;
int a[MAXN], ans[MAXN];
int ver[MAXN << 1], nxt[MAXN << 1], head[MAXN], tot;
inline void add(int x, int y)
{
ver[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
struct node
{
vector<int> v;
int mx, id;
inline void insert(int x)
{
v.push_back(x);
if(mx < x)
{
mx = x;
id = v.size();
}
}
inline void out()
{
for(register int i = 0; i < v.size(); ++i)
if(v[i] == mx)
{
id = i;
}
for(register int i = 0; i < v.size(); ++i)
{
if(i >= id)
cout << v[i] << " ";
}
for(register int i = 0; i < v.size(); ++i)
{
if(i < id)
cout << v[i] << " ";
}
}
}num[MAXN];
int tt;
bool cmp(node a, node b)
{
return a.mx < b.mx;
}
bool vi[MAXN];
void dfs(int x)
{
num[tt].insert(x);
vi[x] = 1;
for(register int i = head[x]; i; i = nxt[i])
{
if(!vi[ver[i]])
dfs(ver[i]);
}
}
signed main()
{
#ifdef lky233
freopen("t.in", "r", stdin);
#endif
cin >> op >> n;
for(register int i = 1; i <= n; ++i)
cin >> a[i];
if(op == 1)
{
for(register int i = 1; i <= n; ++i)
add(i, a[i]);
for(register int i = 1; i <= n; ++i)
{
if(!vi[i])
{
++tt;
dfs(i);
}
}
sort(num + 1, num + tt + 1, cmp);
cerr << tt << endl;
for(register int i = 1; i <= tt; ++i)
num[i].out();
return 0;
}
else
{
vector<int> v;
int ls = a[1];
for(register int i = 1; i < n; ++i)
{
v.push_back(a[i]);
if(a[i + 1] > ls && a[i + 1] > a[i])
{
ls = a[i + 1];
if(!ans[v[v.size() - 1]])
{
ans[v[v.size() - 1]] = v[0];
}
for(register int j = 1; j < (int)v.size(); ++j)
{
if(!ans[v[j - 1]])
ans[v[j - 1]] = v[j];
}
v.clear();
}
}
v.push_back(a[n]);
if(!ans[v[v.size() - 1]])
{
ans[v[v.size() - 1]] = v[0];
}
for(register int j = 1; j < (int)v.size(); ++j)
{
if(!ans[v[j - 1]])
ans[v[j - 1]] = v[j];
}
for(register int i = 1; i <= n; ++i)
{
if(ans[i] == 0)
{
puts("-1");
return 0;
}
}
for(register int i = 1; i <= n; ++i)
cout << ans[i] << " ";
}
}
E chessboard
problem
在\(n*m\)的棋盘中,挖掉两个格子,使得可以用\(1*2\)或者\(2*1\)的骨牌不重叠不遗漏覆盖满除两个被挖掉格子之外的其他格子。输出挖掉两个格子的方案数。
sov
打死也没想到是个结论题
考虑进行黑白染色:
如果\(n\equiv 1\pmod2 \land m \equiv 1 \pmod2\),无法进行染色,\(ans = 0\);
如果\(n = 1, ans = \sum_{i = 1}^{m / 2}\),
如果\(m = 1, ans = \sum_{i = 1}^{n / 2}\)
剩下的就是染色,黑白均分对棋盘染色,从黑色拿掉一个,白色拿掉一个,\(ans = (\frac{n * m}{2})^2\)
code
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
if(n & 1 && m & 1)
{
puts("0");
return 0;
}
if(n == 1)
{
m /= 2;
cout << (m * (m + 1) / 2) << endl;
return 0;
}
if(m == 1)
{
n /= 2;
cout << (n * (n + 1) / 2) << endl;
return 0;
}
cout << (n * m / 2) * (n * m / 2) << endl;
}