21杭电多校第四场
A
签到题,显然只有当所有系数都\(C=0\)才能收敛,判一下即可
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-b+MOD)%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
char s[110];int n,ans,l,r;
int main()
{
rep(T,1,read())
{
scanf("%s",s+1);n=strlen(s+1);l=1;ans=1;
rep(i,1,n+1) if(i==n+1||s[i]=='+')
{
int flg=1;r=i-1;
if(s[l]!='0') {ans=0;break;}
l=i+1;
}
puts(ans?"YES":"NO");
}
}
B
签到题,每次\(dfs\)开一个数组记录目前每种颜色的个数,进\(+1\),出\(-1\)
这样得到所有\(a_{i,j}\) 累加即可
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 2200
#define MOD1 1000000007
#define MOD2 1000000009
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls1(a,b) (a+b)%MOD1
#define pls2(a,b) (a+b)%MOD2
#define mns(a,b) (a-b+MOD)%MOD
#define mul1(a,b) (1LL*(a)*(b))%MOD1
#define mul2(a,b) (1LL*(a)*(b))%MOD2
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int pw[MAXN][2],n,col[MAXN],nxt[MAXN<<1],fst[MAXN],to[MAXN<<1],res[MAXN],cnt,ans1,ans2,tot;
void add(int u,int v){nxt[++cnt]=fst[u],fst[u]=cnt,to[cnt]=v;}
void dfs(int x,int pa)
{
res[col[x]]++;if(res[col[x]]==1) tot++;
ans1=pls1(ans1,mul1(pw[x-1][0],tot));
ans2=pls2(ans2,mul2(pw[x-1][1],tot));
ren if(to[i]^pa) dfs(to[i],x);
res[col[x]]--;if(res[col[x]]==0) tot--;
}
int main()
{
pw[0][0]=pw[0][1]=1;rep(T,1,read())
{
n=read();int a;
rep(i,2,n) {a=read(),add(a,i),add(i,a);}
rep(i,1,n) col[i]=read(),
pw[i][0]=mul1(pw[i-1][0],19560929),pw[i][1]=mul2(pw[i-1][1],19560929);
rep(i,1,n) {dfs(i,0);printf("%d %d\n",ans1,ans2);ans1=ans2=0;}
rep(i,1,n) fst[i]=0;tot=cnt=0;
}
}
C
对所有长度为\(n\)的串,设\(f(i)\)表示最小周期为\(i\)的串的个数,则\(ans=\sum\limits_{i=1}^nf(i)\lfloor\frac{n}{i}\rfloor\)
有一个朴素的求\(f(i)\)的想法即为\(f(n)=2^n-\sum\limits_{i|n,i<n}f(i)\)
这个式子本身是没有什么问题的,但是在计算\(ans\)的时候,对于\(\lfloor\frac{n}{i}\rfloor=1\)的情况,可能会计算重复
例如\(11011\)这个串,在最小周期为\(3,4,5\)时即作为\(110\ 11\),\(1101\ 1\)和\(11011\)分别被计算了一次
而\(\lfloor\frac{n}{i}\rfloor\ge 2\)时,则没有这个问题,因为若有不同的重复一定会出现更小的循环节
因此考虑吧\(\lfloor\frac{n}{i}\rfloor=1\)即\(i>\lfloor\frac{n}{2}\rfloor\)的部分单独拿出来计算,即
\(ans=\sum\limits_{i=1}^{\lfloor\frac{n}{2}\rfloor}f(i)\lfloor\frac{n}{i}\rfloor+1\cdot (2^n-\sum\limits_{i=1}^{\lfloor\frac{n}{2}\rfloor}f(i))\\ \quad\ \ \ =2^n+\sum\limits_{i=1}^{\lfloor\frac{n}{2}\rfloor}(\lfloor\frac{n}{i}\rfloor-1)f(i)\)
是一个数论分块的形式,考虑如何快速计算\(f(i)\)的前缀和
将\(f(n)=2^n-\sum\limits_{i|n,i<n}f(i)\)移项后得\(f*1=2^n\),已经是杜教筛形式了,直接套
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 1001001
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-(b))%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int f[MAXN];const int lim=1e6;
int qp(int x,int t,int res=1)
{
for(;t;t>>=1,x=mul(x,x)) if(t&1) res=mul(res,x);
return res;
}
void mem(int n=lim)
{
int pw=1;rep(i,1,n)
{
pw=mul(pw,2);f[i]=mns(pw,f[i]);
for(int j=i*2;j<=n;j+=i) f[j]=pls(f[j],f[i]);
}
rep(i,1,n) f[i]=pls(f[i-1],f[i]);
}
unordered_map<int,int> ansf;
int sum(int n)
{
if(n<=lim) return f[n];if(ansf[n]) return ansf[n];
ll res=0;for(int l=2,r;l<=n;l=r+1)
r=n/(n/l),res=pls(res,mul(sum(n/l),r-l+1));
res=mns(qp(2,n+1)-2,res);return ansf[n]=res;
}
int solve(int n,int res=0)
{
for(int l=1,r;l<=(n/2);l=r+1)
r=n/(n/l),res=pls(res,mul((n/l-1),mns(sum(r),sum(l-1))));
return pls(res,qp(2,n));
}
int main()
{
mem();rep(T,1,read())
{
int n=read();printf("%d\n",pls(MOD,solve(n)));
}
}
D
考虑二分答案,然后\(check\)
对于后缀树,因为每个节点的\(right\)集合中对应的串是连续的
处理一下原数组的前缀和,对每个节点再套一个二分,在范围内找到有多少个满足条件的串
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 200100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-b+MOD)%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,a[30],sum[MAXN],rt,las,tot,mxl[MAXN],fa[MAXN],pos[MAXN];ll m;
int tr[MAXN][26],res;char s[MAXN];
void extend(int c,int id)
{
int p=las,np=las=++tot;mxl[np]=mxl[p]+1;pos[np]=id;
for(;p&&!tr[p][c];p=fa[p]) tr[p][c]=np;
if(!p) {fa[np]=rt;return ;}int q=tr[p][c];
if(mxl[q]==mxl[p]+1) {fa[np]=q;return ;}
int nq=++tot;mxl[nq]=mxl[p]+1;pos[nq]=pos[q];
memcpy(tr[nq],tr[q],sizeof(tr[nq]));
fa[nq]=fa[q],fa[np]=fa[q]=nq;
for(;p&&tr[p][c]==q;p=fa[p]) tr[p][c]=nq;
}
int cheq(int x,ll cnt=0)
{
rep(i,2,tot)
{
int l=pos[i]-mxl[i]+1,r=pos[i]-mxl[fa[i]],res=-1,R=r;
for(int mid=l+r>>1;l<=r;mid=l+r>>1)
if(sum[pos[i]]-sum[mid-1]<=x) res=mid,r=mid-1;
else l=mid+1;
if(~res) cnt+=R-res+1;
}
return cnt>=m;
}
int main()
{
rep(T,1,read())
{
rt=las=tot=1;n=read();scanf("%lld",&m);scanf("%s",s+1);
rep(i,0,25) a[i]=read();res=-1;
rep(i,1,n) {extend(s[i]-'a',i);sum[i]=sum[i-1]+a[s[i]-'a'];}
for(int l=1,r=sum[n],mid=l+r>>1;l<=r;mid=l+r>>1)
if(cheq(mid)) res=mid,r=mid-1;
else l=mid+1;
printf("%d\n",res);
rep(i,1,tot) {fa[i]=0;rep(j,0,25) tr[i][j]=0;}
}
}
E
据说可以\(segment\ tree\ beats\)做 学完了就来(
F
不知道是什么的奇怪算法 弃了
G
朴素的做法就是对每个点\(a_i\)前面小于\(a_i\)的数做一个单调栈
\(f_i=\sum f_j\)其中\(j\)为单调栈上的点,若\(a_i\)为一个极小值则\(f_i=1\)
可以使用线段树维护一段区间的单调栈,这样按照权值升序在线段树上修改,每次查询即可
因为合并两个区间的单调栈是从右向左合并的,因此对每个区间肯定要记录的是左端点\(mx\)和单调栈上\(sum=\sum f_i\)
考虑合并的时候,相当于在左端点里要二分找到最靠右符合条件的右端点
定义\(query(l,r,lim)\)表示在\([l,r]\)区间内查询所有\(>lim\)的数组成的单调栈的\(\sum f_i\)
显然每次实际查询即为\(f_i=query(1,i,0)\)
在合并时,若当前\(lim\)大于右子树最大值,则右子树可以被扔掉,否则答案为左子树全部的答案加右子树的部分答案
考虑维护\(left_{ans}\)表示每个区间的\(query(l,mid,rmx)\)即左子树的全部答案
这样可以将查询的复杂度稳定在\(nlog^2\)
需要注意每次查询的时候需要记录一下当前靠右部分的左端点\(mx\)才能进行符合条件的左区间查询
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-b+MOD)%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,g[MAXN],ans,f[MAXN],p[MAXN];
struct node
{
int sum,mx,lft;
void mem(){sum=0,mx=-1,lft=0;}
}tr[MAXN<<2];
void build(int k,int l,int r)
{
tr[k].mem();if(l==r) return ;int mid=l+r>>1;
build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
int nmx;
int query(int k,int l,int r,int a,int b,int w)
{
if(tr[k].mx<w) return 0;
if(l==r) {nmx=max(w,tr[k].mx);return tr[k].mx>w?tr[k].sum:0;}
int mid=l+r>>1,res=0;
if(a<=l&&r<=b)
{
if(w>tr[k<<1|1].mx) return query(k<<1,l,mid,a,b,w);
res=pls(tr[k].lft,query(k<<1|1,mid+1,r,a,b,w));
nmx=max(nmx,tr[k].mx);return res;
}
if(b<=mid) return query(k<<1,l,mid,a,b,w);
else if(a>mid) return query(k<<1|1,mid+1,r,a,b,w);
else
{
res=query(k<<1|1,mid+1,r,a,b,w);
res=pls(res,query(k<<1,l,mid,a,b,nmx));
return res;
}
}
void mdf(int k,int l,int r,int x,int w)
{
if(l==r) {tr[k].mx=w,tr[k].sum=f[w];return ;}int mid=l+r>>1;
x<=mid?mdf(k<<1,l,mid,x,w):mdf(k<<1|1,mid+1,r,x,w);
tr[k].lft=query(k<<1,l,mid,l,mid,tr[k<<1|1].mx);
tr[k].sum=pls(tr[k].lft,tr[k<<1|1].sum);
tr[k].mx=max(tr[k<<1].mx,tr[k<<1|1].mx);
}
int main()
{
rep(T,1,read())
{
n=read();rep(i,1,n) g[i]=read(),p[g[i]]=i;
build(1,1,n);
rep(i,1,n)
{
nmx=0;f[i]=query(1,1,n,1,p[i],0);
if(!f[i]) f[i]=1;mdf(1,1,n,p[i],i);
}
printf("%d\n",query(1,1,n,1,n,0));
}
}
H
开一个\(vector\)表示当前这一行能走到哪些区间
向下一行转移的时候由于障碍物 这个区间可能分裂/删除/端点右移
模拟一下即可
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-b+MOD)%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define x first
#define y second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m,k;ll res;
vector<int> v[MAXN];
vector<pii> ans,tmp;
int main()
{
rep(T,1,read())
{
n=read(),m=read(),k=read();int a,b;res=0;
rep(i,1,n) v[i].clear();ans.clear();
rep(i,1,k) a=read(),b=read(),v[a].pb(b);
rep(i,1,n) if(v[i].size()) sort(v[i].begin(),v[i].end());
if(!v[1].size()) {ans.pb({1,m});res+=m;}
else {ans.pb({1,v[1][0]-1});res+=v[1][0]-1;}
rep(i,2,n)
{
vector<int>::iterator it;
v[i].pb(m+1);int L=0,l,r;
it=v[i].begin();
//cout<<"id: "<<i<<" "<<res<<endl;
for(auto t:ans)
{
//cout<<"t: "<<t.x<<" "<<t.y<<endl;
bg:;
l=max(t.x,L),r=t.y;if(l>r) continue;
while((*it)<l&&it!=v[i].end()) it++;
while((*it)==l&&it!=v[i].end()&&l<=r) l++,it++;
if(l==r+1) continue;
//cout<<l<<" "<<(*it)<<endl;
tmp.pb({l,(*it)-1});L=(*it)+1;goto bg;
}
ans.clear();
for(auto t:tmp) {res+=t.y-t.x+1;ans.pb(t);}
tmp.clear();
}
printf("%lld\n",res);
}
}
I
判断一下每一列是否非空,找到一些连续非空的区间
若区间个数\(>7\),则一定均为汉字产生,合并前面的若干个即可
#include<bits/stdc++.h>
#define inf 2139062143
#define ll long long
#define db double
#define ld long double
#define ull unsigned long long
#define MAXN 100100
#define MOD 998244353
#define Fill(a,x) memset(a,x,sizeof(a))
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pls(a,b) (a+b)%MOD
#define mns(a,b) (a-b+MOD)%MOD
#define mul(a,b) (1LL*(a)*(b))%MOD
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int n=30,m=100;
pii a[15];int tot;
char s[35][110];
int cheq(int x)
{
rep(i,1,n) if(s[i][x]=='#') return 1;
return 0;
}
int main()
{
rep(T,1,read())
{
rep(i,1,n) scanf("%s",s[i]+1);tot=0;
rep(i,1,m) if(cheq(i))
{
int j=i+1;while(cheq(j)) j++;
j--;a[++tot]={i,j};i=j+1;
}
printf("Case #%d:\n",T);
printf("%d %d\n",a[1].fi,a[tot-6].se);
rep(i,tot-5,tot) printf("%d %d\n",a[i].fi,a[i].se);
}
}
J
又是奇怪的算法 弃了
K
莫队 咕了