前言

仅仅是做,并没有实际参与比赛,且F题并没有代码参考。

A

题目

个数非常显然的是$n-\left\lfloor\frac{k}{2}\right\rfloor$

至于为什么,这里证明一下:

首先,在 $[1,k-1]$ 中,你选了一个数字,那么 $k-x$ 的数字就不能选,因此小于 $k$ 中你最多选 $\left\lceil\frac{k-1}{2}\right\rceil$ 个数字,同时我们也能构造出这种方案,就是$\left\lceil\frac{k}{2}\right\rceil$ 一直到 $k-1$ 全部选上。

这样子的话选了 $k-1-\left\lceil\frac{k}{2}\right\rceil+1=\left\lceil\frac{k-1}{2}\right\rceil$

刚刚好,然后把大于 $k$ 的算上就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,k;scanf("%d%d",&n,&k);
int mid=(k+1)/2;
printf("%d\n",n-mid);
for(int j=mid;j<=n;j++)
{
if(j!=k)printf("%d ",j);
}
printf("\n");
}
return 0;
}

B

题目

这道题目就更加简单了,不难发现只有 $0,1,2,5,8$ 会翻转,直接暴力枚举,时间复杂度:$O(625n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
int x,y;//分别表示小时和分钟
};
int n,m;
inline bool check(node x){return x.x<n && x.y<m;}
inline int operator-(node x,node y)
{
if(x.x<y.x || (x.x==y.x && x.y<y.y))x.x+=n;
return (x.x-y.x)*m+x.y-y.y;
}
int yuan[]={0,1,2,5,8};
int huan[]={0,1,5,2,8};
node sh1,sh2;
node ans;int limitans;
node goal;
void dfs(int dep)
{
if(dep==5)
{
if(check(sh2)==1 && check(sh1)==1 && sh1-goal<limitans)limitans=sh1-goal,ans=sh1;
return ;
}
for(int i=0;i<=4;i++)
{
if(dep==1)
{
sh1.y=(sh1.y/10)*10+yuan[i];
sh2.x=(sh2.x%10)+huan[i]*10;
}
else if(dep==2)
{
sh1.y=(sh1.y%10)+yuan[i]*10;
sh2.x=(sh2.x/10)*10+huan[i];
}
else if(dep==3)
{
sh1.x=(sh1.x/10)*10+yuan[i];
sh2.y=(sh2.y%10)+huan[i]*10;
}
else
{
sh1.x=(sh1.x%10)+yuan[i]*10;
sh2.y=(sh2.y/10)*10+huan[i];
}
dfs(dep+1);
}
}
inline void getz(int &x)
{
x=0;char c=getchar();
while(c>'9' || c<'0')c=getchar();
while(c<='9' && c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline void print(int x)
{
if(x<10)printf("0");
printf("%d",x);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
getz(goal.x);getz(goal.y);
sh1.x=0;sh1.y=0;sh2.x=0;sh2.y=0;
limitans=999999999;
dfs(1);
print(ans.x);
printf(":");
print(ans.y);
printf("\n");
}
return 0;
}

C

题目

基本思路就是前面尽量迎合原字符串,显然,从起始位置开始和原字符串连续相同的字符越多的字符串,字典序应该是越小的。

比如说现在我在处理 $i$ 这个位置,那么,我需要干两个事情,第一个,我能不能在这个位置放上一个比这个位置大的字符,如果可以,用 $ans$ 记录下这个位置,然后再判断我现在能不能在这个放上这个位置的字符,如果不可以,直接退出输出答案。

当然,如果原来的字符串就可以满足要求先输出原来的字符串。

输出答案也很简单,再模拟一边,然后模拟到 $ans$ 时填一个尽可能接尽原位置字符的字符,然后如果还可以申请字符,就全部填充上 $a$ ,然后后面从小到达输出。

这里讲一个错误的思路:

如果不用申请字符就可以填充这个位置的话,就不去判断这个位置能不能更新 $ans$ ,这是错误的,因为存在 $z$ 这个字符是无法被超越的,比如下面这个例子:

$n=6,m=2$
$abbzzz$

这个时候,$ans$ 在 $2$ 时被更新以后在 $4$ 往后的位置都不可能被更新,如果不能在 $3$ 更新答案就错了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include<cstdio>
#include<cstring>
#define N 210000
using namespace std;
int need[30],now[30],cnt;
int n,k;
int pre;
char st[N];
inline bool check(int x)
{
if(now[x]==need[x])
{
if(cnt==n)return 0;
need[x]+=k,cnt+=k;
}
now[x]++;
return 1;
}
inline int findc(int limit)
{
if(limit==25)return -1;
else if(cnt<n)return limit+1;
else
{
for(int i=limit+1;i<=25;i++)
{
if(now[i]<need[i])return i;
}
return -1;
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
pre=1;memset(need,0,sizeof(need));
memset(now,0,sizeof(now));cnt=0;
scanf("%d%d",&n,&k);
scanf("%s",st+1);
if(n%k!=0)printf("-1");
else
{
need[st[1]-'a']+=k;
now[st[1]-'a']++;cnt+=k;
bool bk=1;//用来存是否能够相等,主动break=0
for(int i=2;i<=n;i++)
{
int type=0;
if((type=findc(st[i]-'a'))!=-1)
{
if(now[type]<need[type] || cnt<n)pre=i;//考虑一下放他上一个的情况
}
if(check(st[i]-'a')==0)
{
bk=0;
break;
}
}
if(bk==1)printf("%s",st+1);
else
{
memset(need,0,sizeof(need));
memset(now,0,sizeof(now));
cnt=0;
for(int i=1;i<pre;i++)
{
check(st[i]-'a');
printf("%c",st[i]);
}
int shit=findc(st[pre]-'a');
check(shit);
printf("%c",shit+'a');
need[0]+=n-cnt;
for(int i=0;i<=25;i++)
{
while(now[i]<need[i])
{
printf("%c",i+'a');
now[i]++;
}
}
}
}
printf("\n");
}
return 0;
}

D

题目

做这种题目我就比较快了。

可以发现直接离线,然后对于每一种质因子直接做一个线段树判断现在是不是所有的位置都含有这个质因子。

时间复杂度:$O((n+m)\log^2{n})$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include<cstdio>
#include<cstring>
#define N 210000
#define NN 410000
#define NNN 8100000
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
int n,m;
struct node
{
int lc,rc,c;
bool bk;//清空符号。
int lazy;
}tr[NN];int len;
void bt(int l,int r)
{
int now=++len;
if(l<r)
{
int mid=(l+r)>>1;
tr[now].lc=len+1;bt(l,mid);
tr[now].rc=len+1;bt(mid+1,r);
}
}
inline void jian(int x,int k){tr[x].lazy+=k;tr[x].c-=k;}
inline void clea(int x){tr[x].bk=1;tr[x].c=0;tr[x].lazy=0;}
inline void pushdown(int x)
{
if(tr[x].bk)clea(tr[x].lc),clea(tr[x].rc),tr[x].bk=0;
jian(tr[x].lc,tr[x].lazy);jian(tr[x].rc,tr[x].lazy);
tr[x].lazy=0;
}
inline int mymin(int x,int y){return x<y?x:y;}
inline void pushup(int x){tr[x].c=mymin(tr[tr[x].lc].c,tr[tr[x].rc].c);}
void change(int now,int l,int r,int k)
{
pushdown(now);
if(l==r){tr[now].c++;return ;}
int mid=(l+r)/2;
if(k<=mid)change(tr[now].lc,l,mid,k);
else if(mid<k)change(tr[now].rc,mid+1,r,k);
pushup(now);
}
int minin[N];//最小的因子
struct Query
{
int y,hou,ti/*表示加入的时间*/;
}b[NNN];int dnt,lbst[N],hea[N];
inline void ins(int x,int y,int ti)
{
dnt++;if(!hea[x])hea[x]=dnt;
b[dnt].y=y;b[dnt].ti=ti;b[lbst[x]].hou=dnt;lbst[x]=dnt;
}
LL pre[N];//保存答案用的
void work(int val)
{
clea(1);
for(int k=hea[val];k;k=b[k].hou)
{
int y=b[k].y;
change(1,1,n,y);
if(tr[1].c==1)
{
pre[b[k].ti]*=(LL)val;
pre[b[k].ti]%=mod;
jian(1,1);
}
}
}
int sta[N],top;
bool v[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)pre[i]=1;
for(int i=2;i<=200000;i++)
{
if(!v[i])
{
minin[i]=i;
sta[++top]=i;
}
for(int j=1;j<=top && sta[j]*i<=200000;j++)
{
v[i*sta[j]]=1;
minin[i*sta[j]]=sta[j];
if(i%sta[j]==0)break;
}
}

//找到最小的因子
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
while(x>1)
{
ins(minin[x],i,1);
x/=minin[x];
}
}
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
while(y>1)
{
ins(minin[y],x,i);
y/=minin[y];
}
}
bt(1,n);
for(int i=1;i<=top;i++)work(sta[i]);
for(int i=2;i<=m;i++)pre[i]=(pre[i]*pre[i-1])%mod;
for(int i=1;i<=m;i++)printf("%lld ",pre[i]);
printf("\n");
return 0;
}

E

题目

这道题目的话比较阿巴,我还看错题了(╯‵□′)╯︵┻━┻。

首先,如果首位置不同,那么直接 $0111111…$ 和 $1000000….$ 就可以轻松达到最大。

如果首位置相同,特判一下三种情况:
两个字符串相同,直接输出。
两个字符串除了最后一个位置都相同,直接输出大的字符串。
两个字符串除了最后两个位置都相同,直接输出大的字符串。

至于为什么要特判,看完你应该就可能明白了。(可能判多了)

注:二进制第 $1$ 位为 $x[n]$

首先,一个基本的知识:$f(1,x)=?$

  1. $x$是奇数,那么 $n$ 除了最后一位全部保留,反之全部为 $0$ 。
  2. 如果 $x[n-1]=1,x[n]=0$ 或者 $x[n-1]=0,x[n]=1$ ,那么最后一位为 $1$ ,反之为 $0$ 。

那么,假设字符串在第 $k$ 个位置出现不同,记第 $k$ 个位置为 $1$ 的字符串为 $A$ ,另外一个为 $B$ 。

这里直接说结论,如果 $A$ 中 $k+1$ 到 $n$ 中存在一个为 $1$ 的数字,或者 $B$ 中 $k+1$ 到 $n$ 的位置存在一个位置为 $0$ ,那么答案为 $A$ 的 $1$ 到 $n-1$ 位以及最后一位为 $1$ ,否则为 $A$ 的 $1$ 到 $n-1$ 位以及最后一位为 $0$ 。

至于证明,比较难写下来,建议自己想,其实证明起来比较轻松。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1100000
using namespace std;
int n;
char a[N],b[N];
char t[N];
inline void fu1(int id){for(int i=id;i<=n;i++)t[i]='1';}
int main()
{
// freopen("std.in","r",stdin);
// freopen("vio.out","w",stdout);
scanf("%d",&n);
scanf("%s",a+1);
scanf("%s",b+1);
if(n==1 && a[1]=='0' && b[1]=='0')
{
printf("0\n");
return 0;
}
if(a[1]!=b[1])
{
fu1(1);
printf("%s\n",t+1);
}
else if(a[1]==b[1])
{
int pre=n+1;
for(int i=1;i<=n;i++)
{
if(a[i]==b[i])printf("%c",a[i]);
else
{
if(b[1]=='1')swap(a,b);
pre=i;
break;
}
}
if(pre==n+1)return 0;
if(pre==n)
{
printf("1\n");
return 0;
}
if(pre==n-1)//再特判一种情况
{
printf("%c%c\n",a[n-1],a[n]);
return 0;
}
bool bk=0;
for(int i=pre+1;i<=n;i++)
{
if(b[i]=='0' || (a[i]=='1'))
{
bk=1;
break;
}
}
if(bk)a[n]='1';
for(int i=pre;i<=n;i++)printf("%c",a[i]);
printf("\n");
}
return 0;
}

F

题目

这道题目的要求比较苛刻,我没有做出来(╯‵□′)╯︵┻━┻。

当然,我们可以把行和列独立,并且行和列一定存在一个最小的循环节。

这些都是字符串的基本理论了。

当然,现在检验行,我们存不存在一种方法能够在$\frac{3}{2}\left\lfloor\log{(n+m)}\right\rfloor$的时间内处理出行的最小循环节。

当然是有的,首先,我们把 $m$ 质因数分解成:$\prod\limits_{i=1}^kp_{i}^{a_{i}}$,我们知道,最小循环节肯定是 $m$ 的因数,那么 $\frac{m}{循环节长度}$ 也就是被分成的节的个数也是个整数,我们尝试找出节的个数的最大值,也就是尝试能不能将字符串分成 $p_{i}^{t}(1≤i≤k,t≤a_{i})$个循环节,然后就可以得到节的个数的最大值的因子,然后乘起来即可。

至于判断能不能将字符串分成 $p_{i}^{t}(1≤i≤k,t≤a_{i})$个循环节,我们转化为一下问题,已知有 $k$ 个数字,如何花最小的步数判断他们相等。

如果 $k=2$ ,那么直接判断。

如果 $k>2$ ,如果 $k$ 是偶数,则仍然只用判断 $1,2$ 号数字,因为 $\frac{k}{2}$ 块肯定检验通过了,如果 $k$ 是奇数,那么设 $y$ 满足$2y+1=k$ ,那么只用比较 $[1,y],[y+1,k-1],[y+2,k]$ ,至于为什么,知道 $kmp$ 找循环节的人应该都知道,不知道的话我也讲不清楚。

至于为什么能过,因为这样的最坏次数是:$2log_{3}{n}=\log_{3}{4}*\log_{2}{n}$($log$向下取整),显然满足要求。

横竖都做一遍即可。