Div1的前三题和NOIP提高组可能难度差不多(或菜一点),就拿来做模拟了。
D和E当作练习,然后这个E真的是打的我心态爆炸(最后弃疗了)。
原题地址
为什么Markdown没有折叠代码片的功能啊!
【题目大意】
给定一个含有’(‘,’)’,’?’的字符串,其中’?’必须代替其他两种字符的一中。问有多少个区间[l,r]满足这个区间可以是一个合法的括号序列。
【解题思路】
考虑一段区间 [l,r] [ l , r ] 是合法的充要条件:
1.[l,l],[l,l+1]...[l,r] 1. [ l , l ] , [ l , l + 1 ] . . . [ l , r ] 每个区间满足左括号数+问号数>=右括号数
2.[r,r],[r−1,r]...[l,r] 2. [ r , r ] , [ r − 1 , r ] . . . [ l , r ] 每个区间满足右括号数+问号数>=左括号数
两重循环扫即可。
#include
using namespace std;
const int N=5005;
int n,cl,cr,ct,ans;
int vis[N][N];
char s[N];
void trans(char c)
{
if(c=='(') ++cl;
else if(c==')') ++cr;
else ++ct;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF917A.in","r",stdin);
freopen("CF917A.out","w",stdout);
#endif
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;++i)
{
cl=cr=ct=0;
for(int j=i;j<=n;j++)
{
trans(s[j]);
if(cl+ctbreak;
++vis[i][j];
}
}
for(int i=n;i;--i)
{
cl=cr=ct=0;
for(int j=i;j;--j)
{
trans(s[j]);
if(cr+ctbreak;
++vis[j][i];
}
}
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j)
if(vis[i][j]==2 && (j-i)%2) ++ans;
printf("%d\n",ans);
return 0;
}
【题目大意】
两个人在一幅带小写字母权值的有向图上,分别 i i 和 j j 出发(可以相同)。轮流行动,每次所走的边权必须大于等于上一个人所走的边权,第一个无法走的人失败另一个人获胜,输出所有起点 i i 和 j j 的获胜情况。
【解题思路】
设 f[i][j][las] f [ i ] [ j ] [ l a s ] 为先手在 i i ,后手在 j j ,后手上一步走的是 las l a s 。
枚举两个人的起点爆搜即可。
#include
using namespace std;
const int N=105;
int n,m,tot;
int head[N],f[N][N][28];
struct Tway{int v,w,nex;}e[N*N];
void add(int u,int v,int w) {e[++tot]=(Tway){v,w,head[u]};head[u]=tot;}
int dfs(int x,int y,int las)
{
if(~f[x][y][las]) return f[x][y][las];
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v,w=e[i].w,t=-1;
if(w>=las) t=dfs(y,v,w);
if(!t) f[x][y][las]=1;
}
if(!~f[x][y][las]) f[x][y][las]=0;
return f[x][y][las];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF917B.in","r",stdin);
freopen("CF917B.out","w",stdout);
#endif
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
int u,v;char s[2];
scanf("%d%d%s",&u,&v,s);
add(u,v,s[0]-'a'+1);
}
memset(f,-1,sizeof(f));
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
dfs(i,j,0);
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=n;++j)
putchar(f[i][j][0]?'A':'B');
return 0;
}
【题目大意】
有一行 n n 个石头, x x 只青蛙要从最左边的 x x 个石头跳到最右边的 x x 个石头上。每次只能最左边的蛙跳不超过 k k 的距离,跳 i i 距离花费 ci c i ,一块石头上不能同时站两只蛙。其中还有有 q q 个特殊石头,跳到上面会增加 wi w i 花费(可能是负数)。求最小花费。 x≤k≤8,n≤1e8 x ≤ k ≤ 8 , n ≤ 1 e 8
【解题思路】
如果没有 q q 个特殊石头的限制,因为 K K 很小,我们可以状压一个右边的 K K 个石头,因为一共 x x 个蛙,那么转移是 Cxk∗k C k x ∗ k 的,这里最多560.
观察转移的形式,实际上就是一个(min,+)矩阵乘法。
那么限制有 q q 个特殊石头也不难想到,一个特殊石头只能影响前后 K K 个石头,那么我们只需要在这些地方“刹车”,暴力处理这部分的答案即可。
#include
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N=75,S=(1<<8);
const LL INF=(LL)(1ll<<61);
int X,K,n,m,tot,now;
int cnt[S],mp[S],id[N],c[N];
LL sum;
pii p[N];
struct Matrix
{
LL a[N][N];
Matrix() {for(int i=0;i0,++i) for(int j=0;jvoid init() {for(int i=1;i<=tot;++i) for(int j=0;jfor(int i=1;i<=tot;++i)
for(int k=1;k<=tot;++k)
for(int j=1;j<=tot;++j)
ret.a[i][j]=min(ret.a[i][j],x.a[i][k]+y.a[k][j]);
return ret;
}
Matrix qpow(Matrix x,int y)
{
Matrix ret;
for(;y;y>>=1,x=mul(x,x)) if(y&1) ret=mul(ret,x);
return ret;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF917C.in","r",stdin);
freopen("CF917C.out","w",stdout);
#endif
scanf("%d%d%d%d",&X,&K,&n,&m);
for(int i=1;i<=K;++i) scanf("%d",&c[i]);
for(int i=1;i<(1<>1]+(i&1);
if(cnt[i]==X) mp[i]=++tot,id[tot]=i;
}
for(int i=1;i<=tot;++i) for(int j=1;j<=tot;++j) a.a[i][j]=INF;
for(int i=1;i<=tot;++i)
{
if(id[i]&1)
{
for(int j=1;j<=K;++j)
if(!(id[i]&(1<1<>1]]=c[j];
}
else a.a[i][mp[id[i]>>1]]=0;
}
for(int i=1;i<=m;++i) scanf("%d%d",&p[i].fi,&p[i].se);
sort(p+1,p+m+1);
now=1;
for(int i=1;i<=m;++i)
{
if(p[i].fi>=n-X+1) {sum+=p[i].se;continue;}
ans=mul(ans,qpow(a,p[i].fi-now));now=p[i].fi;
for(int j=1;j<=tot;++j) if(id[j]&1)
for(int k=1;k<=tot;++k) ans.a[k][j]+=p[i].se;
}
ans=mul(ans,qpow(a,n-X-now+1)); sum+=ans.a[1][1];
printf("%lld\n",sum);
return 0;
}
【题目大意】给出一棵 n n 个节点的生成树,问 n n 个节点的完全图的所有生成树中,和这棵生成树恰好有 i i 条边重合的答案,输出 i=1...n i = 1... n 的所有答案。
【解题思路】
设树为 T T ,从暴力出发,在完全图中,我们枚举可能选的 T T 的 K K 条边,然后屏蔽掉剩下的 T T 的边,在基尔霍夫矩阵上随便搞一个余子式,即可求出剩下的图的生成树个数。此时,求出的是相似度至多为 K K 的方案。
考虑带权生成树计数的做法,此时我们的基尔霍夫矩阵不再是度数矩阵减邻接矩阵,而是边权和矩阵减去边权矩阵。这里一颗生成树的贡献是边权的乘积。
类似的,对于 T T 的边,标记其边权为 x x ,否则标记为1。仍然对他求行列式,可以得出一个多项式,发现多项式 i i 次项的系数,就是相似度为i时的生成树方案。
由于不能暴力求含未知数的行列式,我们将 x x 带 n n 个常数进去算,然后得出点值再拉格朗日插值回来或者高斯消元,也可以算出答案。
时间复杂度 O(n4) O ( n 4 )
#include
using namespace std;
typedef long long LL;
const int N=105,mod=1e9+7;
int n,len;
LL inv[N],fac[N],ifac[N],ans[N],p[N];
LL a[N][N],mp[N][N];
LL qpow(LL x,LL y)
{
LL ret=1;
for(;y;y>>=1,x=x*x%mod) if(y&1) ret=ret*x%mod;
return ret;
}
void up(LL &x,LL y) {x+=y;if(x>=mod)x-=mod;if(x<0)x+=mod;}
LL gauss(int n)
{
LL ret=1,inp;
for(int i=1,id;i<=n;++i)
{
for(id=i;id<=n && !a[id][i];++id);
if(id>n) return 0;
if(id^i) { for(int j=1;j<=n;++j) swap(a[i][j],a[id][j]); ret=-ret;}
ret=ret*a[i][i]%mod;inp=qpow(a[i][i],mod-2);
for(int j=i+1;j<=n;++j)
{
if(!a[j][i]) continue;
LL tmp=a[j][i]*inp%mod;
for(int k=i;k<=n;++k) up(a[j][k],-a[i][k]*tmp%mod);
}
}
up(ret,mod);
return ret;
}
void calc(int x)
{
memset(a,0,sizeof(a));
for(int i=1;ifor(int j=i+1;j<=n;++j)
{
LL t=mp[i][j]?x:1;
a[i][j]-=t;a[j][i]-=t;a[i][i]+=t;a[j][j]+=t;
}
//for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);puts("");
p[len=0]=gauss(n-1)*ifac[x-1]%mod*ifac[n-x]%mod;
if((n-x)&1) p[0]=mod-p[0];
for(int i=1;i<=n;++i)
{
if(x==i) continue;
LL t=mod-i;
for(int j=++len;~j;--j) p[j]=(t*p[j]+(j?p[j-1]:0))%mod;
}
//for(int i=0;i<=len;++i) printf("%d ",p[i]);puts("");
for(int i=0;i<=len;++i) up(ans[i],p[i]),p[i]=0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF917D.in","r",stdin);
freopen("CF917D.out","w",stdout);
#endif
scanf("%d",&n);fac[0]=ifac[0]=1;
for(int i=1;i<=n;++i) inv[i]=qpow(i,mod-2),fac[i]=fac[i-1]*i%mod,ifac[i]=ifac[i-1]*inv[i]%mod;
for(int i=1;iint u,v;scanf("%d%d",&u,&v);
mp[u][v]=mp[v][u]=1;
}
for(int i=1;i<=n;++i) calc(i);
for(int i=0;iprintf("%lld ",ans[i]);
return 0;
}
【题目大意】
出题人:我就是要毒瘤毒瘤毒瘤
给定一棵 n n 个节点带小写字母边权的树,再给出 m m 个字符串, q q 个询问 u−>v u − > v 路径形成的字符串中,第 i i 个字符串出现了多少次(开头位置不同计数,如 ababa a b a b a 中 aba a b a 出现了2次)。
【解题思路】
毒瘤题!写过这么多题中,最毒瘤的一道。建议先去看金策的论文《字符串算法选讲》,里面有很多有意思的性质。
首先单独考虑一个询问,设 f f 为 lca(u,v) l c a ( u , v ) ,先一个串在考虑 u−>f u − > f 和 f−>v f − > v 的出现次数。
我们可以对这 m m 个串以及它们的反串建AC自动机。
那么若 Si( S i ( 或其反串)在一个位置u到根节点的路径形成的串中作为一个后缀,则u在自动机上的位置在fail树的 Si S i 的子树内。因此这部分我们可以用线段树或者BIT维护dfs序。
重点在于考虑 u−>f−>v u − > f − > v 的贡献,那么 Si S i 一定覆盖在 u−>f u − > f 的后半路径和 f−>v f − > v 的前半路径,即分别以前缀和后缀的形式出现。接下来我们只需要知道 u−>f u − > f 的一个 border b o r d e r 能匹配的最长前缀和 f−>v f − > v 的 border b o r d e r 匹配的最长后缀即可。
引理:对于 kmp k m p 的 fail f a i l 链( x,failx,failfailx,...,0) x , f a i l x , f a i l f a i l x , . . . , 0 ) ,会形成 logx l o g x 个等差数列。
这个在金策的论文上有。因此我们只需要做log次等差数列求交,就可以有多少个位置匹配。
现在的问题变为求 Lmax,Rmax L m a x , R m a x ,假设现在要求 Lmax L m a x ,那么我们建出 Si S i 的反串的后缀树,在后缀树上按 f−>u f − > u 走到某个终止节点,这个终止节点上方最近的一个后缀出现的位置即为最长的后缀,反过来就是 Lmax L m a x 。
接下来考虑代替后缀树,原树上终止节点的位置满足 f−>u f − > u 和某个后缀的 lcp l c p 最长,那么这个位置一定是字典序小于等于 f−>u f − > u 的最后一个位置,我们用 SAM S A M 在 parent p a r e n t 树上反着跳就可以求出这个位置。
接下来怎么找最近呢?后缀树上的两个后缀的父子关系满足父亲既是儿子的前缀,又是儿子的后缀,即对应这个后缀的 border b o r d e r ,可以用 hash+ h a s h + 倍增找到最近的点。
分析到这里发现实际上 SAM S A M 也是不需要的了,我们可以去掉这一步- -。
时间复杂度是 O((n+q)log2n) O ( ( n + q ) l o g 2 n ) 的,然后就是码码码不回头。
#include
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<char,int> pci;
typedef pair<int,char> pic;
typedef pairint> pli;
typedef pair<int,int> pii;
typedef pair<int, pii > piii;
const LL INF=(LL)2e18;
const int inf=2e9,mod=1e9+7;
const int N=2e5+10,M=1800010;
const LL bas=233,hsmod=(LL)100000000000031;
const int hsmsk=(1<<22)-1;
LL qpow(LL x,LL y) {LL ret=1; for(;y;y>>=1,x=x*x%mod) if(y&1) ret=ret*x%mod; return ret;}
namespace KMP
{
LL rdown(LL x,LL y) {return y<0?rdown(-x,-y):(x>0?x/y:-(-x+y-1)/y);}
LL rup(LL x,LL y) {return -rdown(-x,y);}
struct sub
{
int s,d,cnt;
sub(int S=0,int D=0,int CNT=0) {s=S;d=D;cnt=CNT;}
int end()const{return s+(cnt-1)*d;}
bool have(int x)const{return x>=s && x<=end() && !((x-s)%d);}
int calc(const sub &t)
{
if(d==t.d)
{
if((s-t.s)%d) return 0;
return max(0,(min(end(),t.end())-max(s,t.s))/d+1);
}
else if(cnt==1) return t.have(s);
else return have(t.s);
}
sub flip(int x) {return sub(x-(s+(cnt-1)*d),d,cnt);}
};
vector<int> fail[N],slink[N];
void construct(char s[],int n,int id)
{
vector<int> &f=fail[id],&g=slink[id];
f.resize(n+1);g.resize(n+1);f[0]=f[1]=0;
for(int i=2;i<=n;++i)
{
int &p=f[i];p=f[i-1];
while(p && s[p+1]^s[i]) p=f[p];
if(s[p+1]==s[i]) ++p;
}
for(int i=1;i<=n;++i) g[i]=f[i]==0?0:( ((i-f[i])==(f[i]-f[f[i]])) ?g[f[i]]:f[i]);
}
vector extract(int id,int x)
{
vector<int> &f=fail[id],&g=slink[id];
assert(xvectorret;
while(x)
{
int d=x-f[x];
ret.pb(sub(g[x]+d,d,(x-g[x])/d)); x=g[x];
}
reverse(ret.begin(),ret.end());
if(ret.back().cnt>1) ret.back().cnt--,ret.pb(sub(ret.back().end()+ret.back().d,1,1));
return ret;
}
int query(int x,int xl,int y,int yl,int tot)
{
if(!xl || !yl || xl+ylreturn 0;
vector vx=extract(x,xl),vy=extract(y,yl);
for(int i=0;iint itx=0,ity=0,ret=0;
while(itxif(vx[itx].end()<=vy[ity].end()) ++itx;
else ++ity;
}
return ret;
}
};
struct BIT
{
int bit_table[M];
int lowbit(int x) {return x&(-x);}
void add(int x,int v) {for(;xint query(int x) {int ret=0;for(;x;x-=lowbit(x)) ret+=bit_table[x];return ret;}
}bit;
struct HashTable
{
int hs[hsmsk],val[hsmsk];
HashTable() {memset(hs,-1,sizeof(hs));memset(val,-1,sizeof(val));}
int getpos(int p) {return ((p<<5)^(p>>3))&hsmsk;}
int find(int c) {int p=getpos(c);while(~hs[p] && hs[p]^c) p=(p+1)&hsmsk; return val[p];}
int& get(int c)
{
int p=getpos(c); while(~hs[p] && hs[p]^c) p=(p+1)&hsmsk;
if(!~hs[p]) hs[p]=c,val[p]=-1; return val[p];
}
};
namespace ACM
{
int tot=1,fa[M],pc[M]; LL hs[M];
pli hsarr[M]; vector go[M];
HashTable mp;
int insert(int p,int c)
{
int &q=mp.get(p<<8|c);
if(q==-1)
{
q=++tot;fa[q]=p;pc[q]=c;hs[q]=(hs[p]*bas+c+1)%hsmod;
go[p].pb(mkp(c,q));
assert(totreturn q;
}
int ind,fail[M],idfn[M],idfnr[M];
vector<int> con[M];
void idfs(int x)
{
idfn[x]=++ind;
for(int i=0;iint son[M],top[M],dep[M];
set<int> st[M];
int dfs1(int x)
{
int sz=1,mx=0;
for(int i=0;iint v=go[x][i].se;dep[v]=dep[x]+1;
int szv=dfs1(v);sz+=szv;
if(szv>mx) son[x]=v,mx=szv;
}
return sz;
}
void dfs2(int x,int tp)
{
top[x]=tp; if(!son[x]) return;
dfs2(son[x],tp);
for(int i=0;iint v=go[x][i].se;
if(v==son[x]) continue;
dfs2(v,v);
}
}
void construct() {dep[1]=0;dfs1(1);dfs2(1,1);}
void flip(int x)
{
int g=top[x];
if(st[g].find(dep[x])==st[g].end()) st[g].insert(dep[x]);
else st[g].erase(dep[x]);
}
int query(int x)
{
while(x)
{
if(st[top[x]].size()>0 && *st[top[x]].begin()<=dep[x]) return *(--st[top[x]].lower_bound(dep[x]+1));
x=fa[top[x]];
}
return 0;
}
int q[M],qn;
void build()
{
construct();
for(int i=1;i<=tot;++i) hsarr[i]=mkp(hs[i],i);
sort(hsarr+1,hsarr+tot+1);qn=0;q[qn++]=1;
for(int i=0;iint x=q[i];
for(int j=0;j1]=1;
for(int i=1;iint x=q[i],&p=fail[x];
if(fa[x]==1) p=1;
else
{
p=fail[fa[x]];
while(p^1 && !~mp.find(p<<8|pc[x])) p=fail[p];
if(~mp.find(p<<8|pc[x])) p=mp.find(p<<8|pc[x]);
}
con[fail[x]].pb(x);
}
idfs(1);
}
int qans[M],qans2[M];
vector qr[M],qr2[M];
void dfs_solve1(int x)
{
bit.add(idfn[x],1);
for(int i=0;iint pos=qr[x][i].fi;
qans[qr[x][i].se]+=bit.query(idfnr[pos])-bit.query(idfn[pos]-1);
}
for(int i=0;i1);
}
void dfs_solve2(int x,LL hs=0,LL hsbs=1)
{
int p=lower_bound(hsarr+1,hsarr+tot+1,mkp(hs,0))-hsarr;
if(hsarr[p].fi==hs) flip(hsarr[p].se);
for(int i=0;ifor(int i=0;i1))%hsmod,hsbs*bas%hsmod);
if(hsarr[p].fi==hs) flip(hsarr[p].se);
}
void add_query(int ui,int vi,int si,int ti,int id)
{
qr[ui].pb(mkp(ti,id));qr[vi].pb(mkp(si,id));
qr2[si].pb(mkp(ui,id<<1));qr2[ti].pb(mkp(vi,id<<1|1));
}
piii get_query(int id) {return mkp(qans[id],mkp(qans2[id<<1],qans2[id<<1|1]));}
void solve()
{
memset(qans,0,sizeof(qans));
dfs_solve1(1);dfs_solve2(1);
}
};
namespace solution
{
int n,m,q,cur_rt,cur_dp,an;
int qu[N],qv[N],qx[N],uid[N],vid[N];
int dp[N],siz[N],arr[N],gr[N],id[N];
char s[N];
vector con[N]; vector qr[N]; vector<int> sid[N];
void pdfs(int x,int f=-1)
{
siz[x]=1;arr[an++]=x;dp[x]=cur_dp+1;
for(int i=0;iint v=con[x][i].fi;
if(v==f || dp[v]continue;
pdfs(v,x);siz[x]+=siz[v];
}
}
int findroot(int x)
{
an=0;pdfs(x); int ret=x;
for(int i=0;iif(siz[arr[i]]*2>=siz[x] && siz[arr[i]]return ret;
}
void dfs(int x,int f,int g)
{
gr[x]=g;
for(int i=0;iint v=con[x][i].fi;
if(v==f || dp[v]continue;
id[v]=ACM::insert(id[x],con[x][i].se-'a');
dfs(v,x,f==-1?v:g);
}
}
void solve(int X,int d)
{
cur_dp=d;int rt=findroot(X);cur_rt=rt;
dp[rt]=d;id[rt]=1;dfs(rt,-1,rt);
static bool vis[N];
for(int i=0;i1;
for(int i=0;iint x=arr[i];
for(int j=0;jint y=qr[x][j].fi;
if(vis[y] && gr[y]^gr[x]) uid[qr[x][j].se]=id[x],vid[qr[x][j].se]=id[y];
}
}
for(int i=0;i0;
for(int i=0;iint v=con[rt][i].fi;
if(dp[v]continue;
solve(v,d+1);
}
}
void end_of_the_world()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;iint u,v;char c[2]; scanf("%d%d%s",&u,&v,c);
con[u].pb(mkp(v,c[0]));con[v].pb(mkp(u,c[0]));
}
for(int i=1;i<=m;++i)
{
scanf("%s",s+1);int l=strlen(s+1),p=1; sid[i].pb(1);
for(int j=1;j<=l;++j) p=ACM::insert(p,s[j]-'a'),sid[i].pb(p);
KMP::construct(s,l,i);
reverse(s+1,s+l+1);p=1;sid[i+m].pb(1);
for(int j=1;j<=l;++j) p=ACM::insert(p,s[j]-'a'),sid[i+m].pb(p);
KMP::construct(s,l,i+m);
}
for(int i=1;i<=q;++i)
{
int u,v,x;scanf("%d%d%d",&u,&v,&x);
qr[u].pb(mkp(v,i));qu[i]=u;qv[i]=v;qx[i]=x;
}
solve(1,0);
ACM::build();
for(int i=1;i<=q;++i) ACM::add_query(uid[i],vid[i],sid[qx[i]].back(),sid[qx[i]+m].back(),i);
ACM::solve();
for(int i=1;i<=q;++i) piii pp=ACM::get_query(i);
for(int i=1;i<=q;++i)
{
piii pp=ACM::get_query(i);int ans=pp.fi;
ans+=KMP::query(qx[i],pp.se.fi,qx[i]+m,pp.se.se,sid[qx[i]].size()-1);
printf("%d\n",ans);
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF917E.in","r",stdin);
freopen("CF917E.out","w",stdout);
#endif
solution::end_of_the_world();
return 0;
}
【总结】
还行。