hdu2021多校4

比赛链接

1001

签到题,给出的项除了常数以外都是不收敛的。

于是判断有 $x$ 的项是否为 $0$ 即可。

1002

签到题,暴力枚举起始点,用桶记录个数即可。

也可套用点分治做法做到 $O(n\log n)$。

1003

题意

求所有长度为 $n$ 的01字符串的最短循环节的出现次数。

$n\le 10^9,\sum n\le 10^{10}$。

题解

设 $g_n$ 表示答案,$f_i$ 表示最小循环节长度为 $i$ 的字符串个数。

有:$g_n=\sum_{i=1}^n\left \lfloor \frac{n}{i} \right \rfloor f_i$。

考虑 $f_i$ 如何求:

当 $m\le \frac{n}{2}$ 时,$2^m=\sum_{i|m} f_i$。

而当 $m>\frac{n}{2}$ 后,上式就不成立了。因为周期引理不再适用。

但可以发现,此时的 $\left \lfloor \frac{n}{i} \right \rfloor=1$ ,于是我们还是通过补集转换,转去算 $2^n-\sum_{i=1}^{\left \lfloor \frac{n}{2} \right \rfloor} f_i$ 。

于是有 $g_n=2^n+\sum _{i=1}^{\frac{n}{2}}(\left \lfloor \frac{n}{i} \right \rfloor -1)f_i$。

整除分块,又因为有 $2^n=f * 1$,杜教筛筛出 $f_i$ 的前缀和即可。发现跑一次算 $f_i$ 前缀和的杜教筛就能算出整除分块里的所有的前缀和。

这里记录一下杜教筛的过程:若有积性函数 $f$,要算 $f$ 的前缀和 $S_n=\sum_{i=1}^nf_i$找到另外两个积性函数 $h,g$ 使得 $h=f * g$。

$$
\sum_{i=1}^n h_i=\sum_{i=1}^n \sum_{j|i} f(j)g(\frac{i}{j})\
=\sum_{i=1}^n g_i \sum_{j=1}^{\left \lfloor \frac{n}{i} \right \rfloor} f_i\
=\sum_{i=1}^n g_i S_{\left \lfloor \frac{n}{i} \right \rfloor}
$$

于是就可以了。

需要预处理 $O(n^{\frac{2}{3}})$ 前的 $f_i$ 的前缀和。

程序

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
#include <bits/stdc++.h>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define ff(i,j,k) for(int i=(j),end_i=(k);i< end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
#define DEBUG(x) cerr<<#x<<"="<<x<<endl
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&-(x))
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define mp make_pair
#define fi first
#define se second
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
#define CASET fo(___,1,read())
const ll mod=998244353;
inline ll Add(ll x,ll y){x+=y; return (x<mod)?x:x-mod;}
inline ll Dec(ll x,ll y){x-=y; return (x<0)?x+mod:x;}
inline ll Mul(ll x,ll y){return x*y%mod;}
inline ll Pow(ll x,ll y)
{
y%=(mod-1);ll ans=1;for(;y;y>>=1,x=x*x%mod)if(y&1) ans=ans*x%mod;
return ans;
}

const int N=1e6;

int s[N+3],f[N+3];
inline void init(int n)
{
ll nw=1;
fo(i,1,n)
{
nw=(nw*2)%mod;
f[i]=Dec(nw,f[i]);
s[i]=Add(s[i-1],f[i]);
for(int j=i+i;j<=n;j+=i)
f[j]=Add(f[j],f[i]);
}
}
int n,n2,tot;
ll a[300000];
inline int id(int x) {return x<=n2?x:tot-n/x;}

ll calc_g(int m)
{
if(!m) return 0;
if(a[id(m)]!=-1) return a[id(m)];
ll ans=Dec(Pow(2,(m+1)),2);
for(int l=2,r;l<=m;l=r+1)
{
r=m/(m/l);
ans=Dec(ans,1ll*(r-l+1)*calc_g(m/l)%mod);
}
return a[id(m)]=ans;
}
inline ll calc_f(int _n)
{
n=_n; n2=sqrt(n+0.3); tot=1;
for(int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
a[tot++]=((r<=N)?s[r]:-1);
}
int m=n/2;
ll ans=Pow(2,n);
for(int l=1,r;l<=m;l=r+1)
{
r=min(m,n/(n/l));
ans=Add(ans,1ll*(n/l-1)*(calc_g(r)-calc_g(l-1)+mod)%mod);
}
return ans;
}

int main()
{
init(1e6);
CASET printf("%lld\n",calc_f(read()));
return 0;
}

1004

题意

给出一个由小写字母组成的字符串 $S$,及每个字符的权值。一个串的价值为其每个字符的权值之和。

求 $S$ 所有不同的子串中,价值为第 $k$ 小的价值是多少。没有则输出 $-1$。

两个子串相同当且仅当他们长度相同且对应每一位字符均相等。

$n\le 10^5$。

题解

真的是,好久不写,都忘了SAM的性质了。

二分答案,建出SAM后,每个节点对应着 $len_i-len_{fa_i}$ 个子串。且长度连续,均为当中最长串的后缀。

于是在每个节点里面再二分一次找到串的个数就好了。

注意!需要记录SAM中每个串的终止位置!即right集合当中的任意一个即可。

程序

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
#include <bits/stdc++.h>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define ff(i,j,k) for(int i=(j),end_i=(k);i< end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
#define DEBUG(x) cerr<<#x<<"="<<x<<endl
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&-(x))
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define mp make_pair
#define fi first
#define se second
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
#define CASET fo(___,1,read())
const int N=200010;
const int S=26;

namespace SAM{
int ne[N][S],fa[N],len[N],las,siz,idx[N];
inline void init()
{
for(int i=1;i<=siz;i++) memset(ne[i],0,sizeof(ne[i])),fa[i]=len[i]=0;
las=siz=1;
}
inline void extend(int c,int id)
{
int cur=++siz;
idx[cur]=id+1;
len[cur]=len[las]+1;
int p=las;
for(;p&&!ne[p][c];p=fa[p]) ne[p][c]=cur;
if(!p) fa[cur]=1;
else
{
int q=ne[p][c];
if(len[q]==len[p]+1) fa[cur]=q;
else
{
int clone=++siz;
len[clone]=len[p]+1; idx[clone]=idx[q];
memcpy(ne[clone],ne[q],sizeof(ne[q]));
fa[clone]=fa[q];
for(;p&&ne[p][c]==q;p=fa[p]) ne[p][c]=clone;
fa[cur]=fa[q]=clone;
}
}
las=cur;
}
}

int n,m,sum[N],val[30];
ll k;
char s[N];
int L[N],R[N],id[N];
inline ll get()
{
ll ss=0;
m=SAM::siz;
fo(i,2,SAM::siz) id[i]=SAM::idx[i],L[i]=SAM::len[SAM::fa[i]]+1,R[i]=SAM::len[i],ss+=SAM::len[i]-SAM::len[SAM::fa[i]];
return ss;
}

inline ll check(int x)
{
ll ss=0;
fo(i,2,m) if(sum[id[i]]-sum[id[i]-L[i]]<=x)
{
int l=L[i],r=R[i],mid;
for(;l<=r;)
{
mid=(l+r)>>1;
if(sum[id[i]]-sum[id[i]-mid]<=x) l=mid+1;
else r=mid-1;
}
ss+=(r-L[i]+1);
}
return ss;
}
int main()
{
CASET
{
n=read(); cin>>k;
scanf("%s",s);
SAM::init();
ff(i,0,strlen(s)) SAM::extend(s[i]-'a',i);
fo(i,0,25) val[i]=read();
if(get()<k)
{
puts("-1");
continue;
}
fo(i,1,n) sum[i]=sum[i-1]+val[s[i-1]-'a'];
int l=0,r=n*100,mid;
for(;l+1<r;)
{
mid=(l+r)>>1;
if(check(mid)>=k) r=mid;
else l=mid;
}
printf("%d\n",r);
}
return 0;
}

1008

题意

$n\times m$ 的网格图,从 $(1,1)$ 开始,每次能往上或往右走一格,走到 $(n,m)$。有 $k$ 个点不能经过。问有可能走到的格子有多少个。

$n,m,k\le 10^5$。

题解

补集转换,变为求哪些格子走不到。

对于一条斜向上的线段,不能走的格子将形成一个直角三角形或等腰梯形。

考虑斜着的扫描线,这样相当于是若干条不相交的线段组成。随着扫描线的右移,中间的线段长度将集体 $-1$,两端的线段有可能不减。

用链表维护这些线段,插入一个点时判断能否与左右两端的线段合并,或本身就在线段里面。

线段长度减一的话暴力枚举链表中的每一个元素即可,长度减到 $0$ 后将其删除。

这样做的复杂度是对的,因为线段原始的总长不会超过 $k$。

时间复杂度 $O(n+m+k)$。不知为何跑得很慢。

程序

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <bits/stdc++.h>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define ff(i,j,k) for(int i=(j),end_i=(k);i< end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
#define DEBUG(x) cerr<<#x<<"="<<x<<endl
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&-(x))
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define mp make_pair
#define fi first
#define se second
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
#define CASET fo(___,1,read())
const int N=200010;

struct node{
int x,y;
};
node p[N];

vector<int> adj[N];
inline void add(int x,int y){adj[x].pb(y);}

struct line{
int y,len;
};

line s[N<<1];

int n,m,k,L[N],R[N];

inline bool cmp(const int &x,const int &y)
{
return p[x].y>p[y].y;
}

int main()
{
CASET
{
n=read(); m=read(); k=read();
ll ans=1ll*n*m;
fo(i,1,n+m) adj[i].clear();
fo(i,0,n+m+1) L[i]=R[i]=0;
R[0]=n+m+1; L[n+m+1]=0;
fo(i,1,k) p[i].x=read(),p[i].y=read(),add(p[i].x+p[i].y,i);
fo(i,2,n+m) sort(all(adj[i]),cmp);
int u;
fo(i,1,n+m)
{
for(int j=R[0];j!=n+m+1;j=R[j])
{
s[j].len--;
}
if(R[0]!=n+m+1)
{
u=R[0];
if(s[u].y==i-2&&i<=m+1) s[u].len++,s[u].y=i-1;
u=L[n+m+1];
if(s[u].y==s[u].len+1&&i<=n+1) s[u].len++;
}

for(int j=R[0];j!=n+m+1;j=R[j])
{
if(s[j].len==0)
{
L[R[j]]=L[j];
R[L[j]]=R[j];
j=L[j];
}
}
int j=R[0];
s[0].y=1000000000;
for(auto v:adj[i])
{
for(;j!=n+m+1&&s[j].y>=p[v].y;j=R[j]);
j=L[j]; k=R[j];
if(!j)
{
s[v].y=p[v].y; s[v].len=1;
R[0]=L[k]=v;
L[v]=0; R[v]=k;
j=v;
if(k==n+m+1) continue;
}
else
{
if(s[j].y-s[j].len+1<=p[v].y)
{
continue;
}
else
{
if(s[j].y-s[j].len==p[v].y) s[j].len++;
else
{
s[v].y=p[v].y; s[v].len=1;
R[j]=L[k]=v;
L[v]=j; R[v]=k;
j=v;
}
}
}
if(k!=n+m+1)
{
if(s[j].y-s[j].len==s[k].y)
{
s[j].len+=s[k].len;
int t=R[k];
L[t]=j; R[j]=t;
}
}
}

ll k=0;
for(int j=R[0];j!=n+m+1;j=R[j]) ans-=s[j].len,k+=s[j].len;
}
printf("%lld\n",ans);
}
return 0;
}