官方题解
A.Bad Ugly Numbers(1)
想了一下发现,2和3两个元素的神奇之处
1.2只会被偶数整除
2.3不会被2eX整除
结合一下23333333…3便是答案。
(1)特判一下即可。
B.Maximums(1)
发现a[i]相当于给出的,所以这就是一道简单的构造题,维护A数组的最大值即可。
C.Permutation Partitions(1)
1.可以发现对于k段的长度是没有限制的,所以我们可以贪心得到选择的点的坐标。
2.又可以发现,枚举每每两个坐标之间的距离,即是这两个点直接可以得到的方案数,总方案数算个乘积即可。
D.Prefix-Suffix Palindrome(1)
1.思路不难想,先考虑正常的头尾相同的部分,然后可以得到相同部分的长度。
2.再考虑剩下串的最长回文前缀和最长回文后缀即可。
T神写的manachar算法:
#include
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 6e6+5;
char s[N],str[N];
int Len[N],len;
void getstr() {
int k = 0;
str[k++] = '@';
for (int i = 0; i < len; i++) {
str[k++] = '#';
str[k++] = s[i];
}
str[k++] = '#';
len = k;
str[k] = 0;
}
int manacher() {
int mx = 0, id;
int maxx = -1;
for (int i = 1; i < len; i++) {
if (mx > i) Len[i] = min(mx - i, Len[2 * id - i]);
else Len[i] = 1;
while (str[i + Len[i]] == str[i - Len[i]]&&(i+Len[i])<len&&(i-Len[i])>=0) Len[i]++;
if (Len[i] + i > mx) {
mx = Len[i] + i;
id = i;
maxx = max(maxx, Len[i]);
}
}
return (maxx - 1);
}
char s1[N];
char s2[N];
int val[30];
int w[N];
int main() {
int n;
cin>>n;
for(int t=1;t<=n;t++){
scanf("%s",s1);
int l = strlen(s1);
for(int j=0;j<l;j++)
s2[l-1-j] = s1[j];
int dep = 0;
for(int j=0;j<l/2;j++){
if(s1[j]==s2[j]){
dep++;
}
else {
break;
}
}
int ct = 0;
for(int j=dep;j<l-dep;j++){
s[ct++] = s1[j];
}
s[ct++]='\0';
len = strlen(s);
getstr();
int mxx = manacher();
int maxx= 0;
int pos;
for(int i=1;i<len;i++){
if((i-Len[i])==0&&(Len[i]-1)>maxx){
maxx = Len[i]-1;
pos = i;
}
if((i+Len[i])==len&&(Len[i]-1)>maxx){
maxx = Len[i]-1;
pos = i;
}
}
mxx = maxx;
for(int i=0;i<dep;i++) printf("%c",s1[i]);
int r = (pos + mxx)/2 - 1;
int ll = r - mxx + 1;
for(int i = ll ; i <= r ; i++)
{
printf("%c" , s[i]);
}
for(int i=dep-1;i>=0;i--) printf("%c",s1[i]);
printf("\n");
}
return 0;
}
后面看题解是kmp找出的最长回文前缀,感觉很新奇,之前没见过记录一下。
1.首先要知道,next数组可以表示为j位前的子串的最大重复子串的长度。
2.我们将字符串原字符串 S 处理为 S+#+S’。
3.这样从头跑到尾,我们就可以知道最长回文前缀的长度了。
题解代码:
#include
using namespace std;
const int M = (int)(2e6 + 239);
int pref[M], c;
string solve_palindrome(const string& s)
{
string a = s;
reverse(a.begin(), a.end());
a = s + "#" + a;
c = 0;
int i = 1;
pref[0] = 0;
// kmp模板1
while(i < (int)a.size()){
if(a[c] == a[i] || !c) pref[++i] = ++c;
else c = pref[c];
}
// kmp模板2
// c = 0;
// for (int i = 1; i < (int)a.size(); i++)
// {
// while (c != 0 && a[c] != a[i])
// c = pref[c - 1];
// if (a[c] == a[i])
// c++;
// pref[i] = c;
// }
return s.substr(0, c);
}
void solve()
{
string t;
cin >> t;
int l = 0;
while (l < (int)t.size() - l - 1)
{
if (t[l] != t[(int)t.size() - l - 1])
break;
l++;
}
if (l > 0)
cout << t.substr(0, l);
if ((int)t.size() > 2 * l)
{
string s = t.substr(l, (int)t.size() - 2 * l);
string a = solve_palindrome(s);
reverse(s.begin(), s.end());
string b = solve_palindrome(s);
if ((int)a.size() < (int)b.size())
swap(a, b);
cout << a;
}
if (l > 0)
cout << t.substr((int)t.size() - l, l);
cout << "\n";
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
E.Bombs(0)
现场想了半天,感觉用树状数组或线段树之类的维护一个构造的过程,但模型始终建不起来(Orz,太菜了)。
题解说的emm有点难懂,就从个人理解来记录一下(感觉有差分的意思在里面。。。)。
1.将炸弹视作一段从[1,q[i]]的-1串,表示炸弹能影响到的位置。
2.将元素视作段从[1,pos[p[i]]]的串,这样表示,只有q[i] >= pos[a[i]]的时候,这个元素才会被炸掉,不再对答案造成影响。
3.在此基础上我们维护一棵区间更新的最值线段树,当最大值小于等于0的时候,可以视作当前答案不符合要求,将更小的元素加入进来考虑。
上面已经完成了题解的操作。。。下面是自己理解的时候的一点思考。
4.假设次大答案在当前答案的右边,那么它之前所有的炸弹都无法对他造成影响,所以最大值必然大于0,所以次大答案计入贡献。
PS1:如果炸弹位置大于等于次大答案大位置,但已经用来引爆之前的当前答案,可以发现,在[1,pos[当前答案]]的范围内,会增加一次1。因此,可以理解为,被前面元素排掉的炸弹,前面的元素会产生一个大值来确保炸弹已被消耗。
5.假设次大答案在当前答案的左边,那么只用看,[1,pos[c次大答案]]范围内变化即可,有炸弹多出,这一段必然 <= 0,炸弹已被消耗,这一段中必然有 >0 的值
感觉这道题目就是考的建立模型,但水平不够,现在也只能做到理解,感觉有点差分的味道。
PS2:貌似还有O(n)的写法,迟点看看能不能把O(n)的写法补上。
#include
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 3e5+5;
int c[N << 2],add[N << 2],pos[N],q[N],n,x;
inline void pushup(int rt){
c[rt] = max(c[rt << 1],c[rt << 1|1]);
return ;
}
inline void pushdown(int rt){
if(add[rt]){
add[rt << 1] += add[rt];
add[rt << 1|1] += add[rt];
c[rt << 1] += add[rt];
c[rt << 1|1] += add[rt];
}
add[rt] = 0;
return ;
}
inline void update(int L,int R,int val,int rt = 1,int l = 1,int r = n){
if(L <= l && r <= R){
c[rt] += val;
add[rt] += val;
return ;
}
pushdown(rt);
int m = (l+r) >> 1;
if(L <= m) update(L,R,val,lson);
if(m < R) update(L,R,val,rson);
pushup(rt);
return ;
}
int main() {
std::ios::sync_with_stdio(0);cin.tie(0);
cin >> n;
for(int i = 1;i <= n;++i) cin >> x,pos[x] = i;
for(int i = 1;i <= n;++i) cin >> q[i];
int ans = n;
update(1,pos[ans],1);
for(int i = 1;i <= n;++i){
printf("%d%c",ans,i == n ?'\n' :' ');
update(1,q[i],-1);
while(c[1] <= 0 && ans > 1) update(1,pos[--ans],1);
}
return 0;
}
官方题解A到H
A.Kuroni and the Gifts(1)
因为数字数字都不相同,排序后直接输出即可
B.Kuroni and Simple Strings(1)
贪心,用双指针从两边像中间扫,删除简单括弧即可,操作k必为1
C.Kuroni and Impossible Calculation(1)
因为m只有1000,根据鸽笼原理,n>m必为0,不然暴力即可,复杂度上界为m2
D.Kuroni and the Celebration(0)
因为这是一颗树,所以:
1.我们每次选择两个度为1的点进行查询如果查询结果为两个点之一,那么查询结果必为根。
2.如果不相同就把这两个点从图中删去,继续寻找度为1的点。
3.假设一直没有找到,那最后剩下的点只有一个的时候,剩下那个点必然为根。
#include
using namespace std;
typedef long long ll;
const int N = 1e3+5;
const ll M = 10007;
vector <int> e[N];
bool vis[N];
inline int find(int n){
int i;
for(i = 1;i <= n;++i)
if(!vis[i] && e[i].size() == 1)
return i;
}
inline void del(int u){
int v = e[u][0];
for(int j = 0;j < e[v].size();++j){
if(e[v][j] == u){
e[v].erase(e[v].begin()+j);
break;
}
}
return ;
}
int main()
{
int n,u,v,p,q,t,cnt;
scanf("%d",&n);
for(int i = 1;i < n;++i){
vis[i] = 0;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
vis[n] = 0;cnt = n;
while(1){
p = find(n),vis[p] = 1;
q = find(n),vis[q] = 1;
del(p);del(q);cnt -= 2;
printf("? %d %d\n",p,q);
cout.flush();
cin >> t;
if(t == p || t == q || cnt <= 1){
printf("! %d\n",t);
cout.flush();
break;
}
}
return 0;
}
E.Kuroni and the Score Distribution(0)
发现这是一道构造题,但现场没时间写了,我们可以枚举k找规律。
对于3有:
1 + 2 = 3
对于4有:
1 + 3 = 4
对于5有:
1 + 4 = 5
2 + 3 = 5
有此我们可以发现规律,对于任何一个数字k,有(k-1)/2种组合方式。
所以我们可以尝试这样构造数组:
对于m >= (i-1)/2 的情况,我们直接使ans[i] = ans[i-1]+1
对月m < (i-1)/2 的情况,我们考虑如何限制i的组合方式即可,把m作为长度,ans[i] = ans[i-1]+ans[i-m*2]
对于m已经为0,n还没跑完的情况呢? 考虑复制为ans[i-1]+5000,就能保证没有组成他的方式
对于m不为0,但n已经跑完的方式,发现已经不可能构造出符合要求的数组,直接输出-1
PS:大于5000皆可,但要记得数组元素不能大于1000000000,不然会wa。
#include
using namespace std;
typedef long long ll;
const int N = 5e3+5;
int a[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
a[1] = 1;a[2] = 2;
for(int i = 3;i <= n;++i){
if(m){
if(m >= (i-1)/2) m -= (i-1)/2,a[i] = a[i-1]+1;
else a[i] = a[i-1]+a[i-m*2],m = 0;
}
else a[i] = a[i-1]+5000;
}
if(m) printf("-1\n");
else for(int i = 1;i <= n;++i) printf("%d%c",a[i],i == n ?'\n' :' ');
return 0;
}
F.Kuroni and the Punishment(0)
至少到了用取模贪心,但没想到怎么确定检测范围,此处简单转述题解做法:
1.确认答案最多为n次(思路很简单,选择2为最小质因子,那么最多只用操作n次)。
2.至少有n/2个元素的操作,要小于等于1次,因此如果设一个元素为x,那么就要考虑x,x-1,x+1的质因子。
PS:为啥只考虑质因子就可以了,设x可以被两个质数pq分解,那么变成x的操作数和变成能被p整除的操作数是一样的。
3.随机化数组,从中选择S个元素,按2中说的来操作,取出质因子,然后贪心即可。题解里表述错误率低达2-S (随机化算法牛逼)。
//标程(膜拜)
#include
using namespace std;
const int N = 300005, MX = 1E6;
const long long INF = 1E12;
int n, ans = N;
long long a[N];
bool chk[MX];
set<long long> can;
vector<int> pr;
mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
void init() {
for (int i = 2; i < MX; i++) {
if (!chk[i]) {
pr.push_back(i);
for (int j = i; j < MX; j += i) {
chk[j] = true;
}
}
}
}
void add_prime(long long u) {
for (int &v : pr) {
if (u % v == 0) {
can.insert(v);
while (u % v == 0) {
u /= v;
}
}
}
if (u > 1) {
can.insert(u);
}
}
int solve(long long u) {
long long ret = 0;
for (int i = 1; i <= n; i++) {
long long add = (a[i] < u ? u - a[i] : min(a[i] % u, u - a[i] % u));
ret = min((long long) n, ret + add);
}
return ret;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
init();
cin >> n;
vector<int> per;
for (int i = 1; i <= n; i++) {
cin >> a[i];
per.push_back(i);
}
shuffle(per.begin(), per.end(), mt);
for (int i = 0; i < 100 && i < (int)per.size(); i++) {
int u = per[i];
add_prime(a[u]);
add_prime(a[u] + 1);
if (a[u] > 1) {
add_prime(a[u] - 1);
}
}
for (long long v : can) {
ans = min(ans, solve(v));
}
cout << ans;
}
//奆佬非随机化算法(因为造作最多n步,你对一个元素考虑[x-n,x+n]的范围也是可以的)
#include
#pragma GCC optimize ("O2")
#pragma GCC optimize ("unroll-loops")
//#pragma GCC optimize("no-stack-protector,fast-math")
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<pii, int> piii;
typedef pair<ll, ll> pll;
#define debug(x) cerr<<#x<<'='<<(x)<
#define debugp(x) cerr<<#x<<"= {"<<(x.first)<<", "<<(x.second)<<"}"<
#define debug2(x, y) cerr<<"{"<<#x<<", "<<#y<<"} = {"<<(x)<<", "<<(y)<<"}"<
#define debugv(v) {cerr<<#v<<" : ";for (auto x:v) cerr<
#define all(x) x.begin(), x.end()
#define pb push_back
#define kill(x) return cout<
const ld eps=1e-7;
const int inf=1000000010;
const ll INF=10000000000000010LL;
const int mod = 1000000007;
const int MAXN = 1500010, LOG=20;
ll n, m, k, u, v, x, y, t, a, b, ans;
ll A[MAXN], B[MAXN];
bool sieve[MAXN];
bool mark[MAXN];
void upd(ll p){
if (p<2) return ;
if (p<MAXN && mark[p]) return ;
if (p<MAXN) mark[p]=1;
ll tmp=0;
for (int i=1; i<=n; i++){
if (A[i]<=p) tmp+=p-A[i];
else{
ll x=A[i]%p;
tmp+=min(x, p-x);
}
if (tmp>=ans) return ;
}
ans=min(ans, tmp);
}
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
for (int i=2; i*i<MAXN; i++) if (!sieve[i]) for (int j=i*i; j<MAXN; j+=i) sieve[j]=1;
cin>>n;
for (int i=1; i<=n; i++) cin>>A[i];
ans=n;
for (int p=2; p<8000; p++) if (!sieve[p]) upd(p);
sort(A+1, A+n+1); // ??
ll N=0, l=max(A[1]-ans, 2ll), r=A[1]+ans;
for (ll i=l; i<=r; i++) B[N++]=i;
for (ll p=2; p<MAXN; p++) if (!sieve[p]){
for (ll x=(l+p-1)/p*p; x<=r; x+=p){
if (x-l<0 || x-l>=N) continue ;
upd(p);
while (B[x-l]%p==0 && B[x-l]) B[x-l]/=p;
}
}
for (ll i=0; i<N; i++) if (B[i]>1){
upd(B[i]);
}
cout<<ans<<'\n';
return 0;
}
官方题解A到F
A.Grade Allocation(1)
直接班级成绩求和和自己成绩的上限取个最小值即可
B.String Modification(1)
假设选择长度为x,字符串总长为L
1.[x,L]范围内的字符串必然平移到最前方
2.[1,x-1]范围内的字符则往后移动,这个字符串是否发生反转要视翻转次数而定(自己看看马上就懂)
3.按照上面的思路,找切割点直接暴力即可。
C.Primitive Primes(0)
题意很简单,两个多项式求积,问其第几项的系数不能被p整除。写比赛的时候,连fft都用上了,还是没A(精度不够)。
实际上这是一道思维题,我们假设找到F函数里第一个系数不会被p整除的项为i,G函数里第一个系数不会被p整除的项为j,思考他们求和之后的ai+j项,因为i之前的系数都能被p整除,其和任意数的乘积都会被p整除,j项同理。
选出这对i和j就可以保证,ai+j项的系数里,是一对p的倍数和一个不能被p整除的数字组成的,这就保证了ai+j项的系数不能被p整除。
#include
using namespace std;
typedef long long ll;
const int N = 2e5+5;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
ll n,m,p,t,x,y;
bool a = 0,b = 0;
cin >> n >> m >> p;
for(ll i = 0;i < n;++i){
cin >> t;
if(t%p && !a) x = i,a = 1;
}
for(ll i = 0;i < m;++i){
cin >> t;
if(t%p && !b) y = i,b = 1;
}
cout << x+y << endl;
return 0;
}
D.Nash Matrix(0)
现场也是没做出来,一直在纠结从x1,y1到x2,y2假如隔的很远,他们中间都是-1该如何处理,后面看完题解发现是自己想岔了。
1.分析从x1,y1到x2,y2的,因为这是一条有终点有起点的路径,所以这条路径上的点,其坐标必然不可能是(-1,-1),所以,从所有终点开始往外搜即可。
2.分析(-1,-1)的情况,这个当时就想到了,对于每个这种点,建立一个二元组的无限循环即可,旁边的(-1,-1)走进来也能解决问题。
#include
using namespace std;
typedef long long ll;
const int N = 1e3+5;
int x[N][N],y[N][N];
char m[N][N],dir[] = "ULDR";
int mvx[] = {1,0,-1,0};
int mvy[] = {0,1,0,-1};
inline bool check(int x1,int y1,int x2,int y2,char d1,char d2){
if(x[x2][y2] == -1){
m[x1][y1] = d1;
if(m[x2][y2] == '\0') m[x2][y2] = d2;
return 1;
}
return 0;
}
void dfs(int p,int q,char d){
if(m[p][q] != '\0') return ;
m[p][q] = d;
for(int i = 0;i < 4;++i){
int tx = p+mvx[i];
int ty = q+mvy[i];
if(x[p][q] == x[tx][ty] && y[p][q] == y[tx][ty]) dfs(tx,ty,dir[i]);
}
return ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n;
cin >> n;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= n;++j)
cin >> x[i][j] >> y[i][j];
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j){
if(x[i][j] == -1){
bool f = (m[i][j] == '\0');
if(f){
bool f2 = 0;
for(int d = 0;d < 4;++d){
f2 |= check(i,j,i+mvx[d],j+mvy[d],dir[(d+2)%4],dir[d%4]);
}
if(!f2){
cout << "INVALID\n";
return 0;
}
}
}
else if(x[i][j] == i && y[i][j] == j) dfs(i,j,'X');
}
}
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j){
if(m[i][j] == '\0'){
cout << "INVALID\n";
return 0;
}
}
}
cout << "VALID\n";
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j)
cout << m[i][j];
cout << "\n";
}
return 0;
}
题解A到H
A.Even Subset Sum Problem(1)
看数组中是否有一个偶数或者两个奇数即可。
B.Count Subrectangles(1)
思路很简单,求全1矩阵块的数量,找其因子作为一边的长度,然后算两个数组相应达到要求的长度的乘积即可。
C.Unusual Competitions(1)
贪心,对于每个合法段直接跳过,对于每个非法段统计这个非法段的长度,算入贡献。
D.Present(0)
和atcoder一题很像,不过那个是先异或再求和,简单一点。
按题解的思路敲的代码,复杂度更优的没想到。
考虑第K位为1的情况,求和在[(1 << k),(1 << (k+1)))和[(1 << k)+(1 << (k+1)),(1 << (k+2))),范围内的和都是会提供1的,sort之后再二分搜索即可。
#include
using namespace std;
typedef long long ll;
const int N = 4e5+5;
const int M = 25;
int a[N],t[N];
int main()
{
int n,ans = 0;
scanf("%d",&n);
for(int i = 0;i < n;++i) scanf("%d",&a[i]);
for(int i = 0;i < M;++i){
int K = (1 << i),MOD = K << 1;
ll cnt = 0;
for(int j = 0;j < n;++j) t[j] = a[j] % MOD;
sort(t,t+n);
for(int j = 0;j < n;++j){
int up = lower_bound(t,t+n,MOD-t[j]) - t;
int down = lower_bound(t,t+n,K-t[j]) - t;
if(j >= down && j < up) up--;
cnt += up-down;
}
for(int j = 0;j < n;++j){
int up = lower_bound(t,t+n,MOD+K-t[j]) - t;
if(j >= up) up++;
cnt += n-up;
}
cnt /= 2; // 题目握手定理,重复算了两次,所以要除以2
if(cnt & 1) ans |= K;
}
printf("%d\n",ans);
return 0;
}
A.Two Regular Polygons(1)
简单思维题,正多边形里看能不能割出一个边数更少的多边形,看边数能不能整除即可。
B.Bogosort(1)
要求j-a[j]和i-a[i]不能相等,想法很简单,保证a[i]从大到小排序即可。
C.Adding Powers(1)
预处理范围内所有的ki,然后将数组排序后从大到小依次考虑用ki去消即可,可以满足贪心的想法,要注意0的干扰,重复的0是没有关系的。
D.Count the Arrays(0)
题意:构造一个一个先单调递增再单调递减的数组,且其中只有两个数字相同。(现在才发现,这种题目就是摆明了告诉你排列组合推公式,找规律什么的太笨了)。
1.考虑数组选取,m个数字中选n-1个数字:C(n-1,m);
2.考虑选取重复的数字:(n-2);
3.考虑数字的摆放,C(n-3,n-3)+C(n-4,n-3)+…+C(0,n-3) = 2n-3;
可以发现三者组合就得到了答案:C(n-1,m)*(n-2)*2n-3
#include
using namespace std;
typedef long long ll;
const int N = 4e5+5;
const ll M = 998244353;
inline ll qpow(ll a,ll b){
if(b < 0) return 1; // n-3可能小于0,需要特判
ll res = 1;
a %= M;
while(b){
if(b & 1) (res *= a) %= M;
(a *= a) %= M;
b >>= 1;
}
return res%M;
}
inline ll C(ll n,ll m){
if(n == 0 || n == m) return 1;
ll tm = 1,tn = 1;
for(ll i = m;i >= m-n+1;--i) (tm *= i) %= M;
for(ll i = 1;i <= n;++i) (tn *= i) %= M;
return tm*qpow(tn,M-2)%M;
}
int main()
{
ll n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",C(n-1,m)%M*qpow(2,n-3)%M*(n-2)%M);
return 0;
}