优秀的拆分

Luogu
LOJ
UOJ
BZOJ
\(f_i,g_i\)分别表示以\(i\)结尾/开头的形如\(AA\)的串的个数,那么答案就是\(\sum f_ig_{i+1}\)
考虑枚举\(A\)的长度\(len\),那么形如\(AA\)的串一定会过至少两个下标为\(len\)的倍数的位置(我们称之为关键位置)。
对于任意相邻两个关键位置\(i,j=i+len\),它们对答案有\(1\)的贡献当且仅当\(lcp(suf(i),suf(j))+lcs(pre(i-1),pre(j-1))\ge len\)
\(x=lcp(suf(i),suf(j)),y=lcs(pre(i-1),pre(j-1)\),那么这个\(AA\)串的结尾可以落在\([i-y,i+x-len+1)\)
先维护\(f,g\)的差分再前缀和还原即可。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
int min(int a,int b){return a<=b? a:b;}
void swap(int &x,int &y){x^=y^=x^=y;}
const int N=3e4+7;
int t[N],x[N],y[N],Log[N],st[N],ed[N],n;
void init(){memset(x,0,sizeof x),memset(y,0,sizeof y);}
void Init(){memset(st,0,sizeof st),memset(ed,0,sizeof ed);}
struct SuffixArray
{
    int sa[N],rnk[N],h[N][20],m;
    char s[N];
    void rsort()
    {
	for(int i=0;i<=m;++i) t[i]=0;
	for(int i=1;i<=n;++i) ++t[x[y[i]]];
	for(int i=1;i<=m;++i) t[i]+=t[i-1];
	for(int i=n;i;--t[x[y[i]]],--i) sa[t[x[y[i]]]]=y[i];
    }
    void ssort()
    {
	int i,l,p;
	for(i=1;i<=n;++i) x[i]=s[i]-'a'+1,y[i]=i;
	m=26,rsort();
	for(l=1,p=0;p<n;m=p,l<<=1)
	{
	    for(p=0,i=n-l+1;i<=n;++i) y[++p]=i;
	    for(i=1;i<=n;++i) if(sa[i]>l) y[++p]=sa[i]-l;
	    rsort(),swap(x,y),x[sa[1]]=p=1;
	    for(i=2;i<=n;++i) x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+l]==y[sa[i]+l])? p:++p;
	}
    }
    void geth()
    {
	int lcp=0,i,j;
	for(i=1;i<=n;++i) rnk[sa[i]]=i;
	for(i=1;i<=n;++i)
	{
	    if(lcp) --lcp;
	    if(rnk[i]==n) continue;
	    j=sa[rnk[i]+1];
	    while(s[i+lcp]==s[j+lcp]) ++lcp;
	    h[rnk[i]][0]=lcp;
	}
	for(i=1;i<=15;++i) for(j=1;j<=n&&j+(1<<i-1)<=n;++j) h[j][i]=min(h[j][i-1],h[j+(1<<i-1)][i-1]);
    }
    int LCP(int x,int y)
    {
	x=rnk[x],y=rnk[y];
	if(x>y) swap(x,y);
	int k=Log[y-x];    
	return min(h[x][k],h[y-(1<<k)][k]);
    }
}A,B;
int main()
{
    int i,T=read(),j,x,y,t,len;
    LL ans;
    for(i=2;i<N;++i) Log[i]=Log[i>>1]+1;
    while(T--)
    {
	Init(),init(),scanf("%s",A.s+1),n=strlen(A.s+1),A.ssort(),A.geth();
	for(i=1;i<=n;++i) B.s[i]=A.s[n-i+1]; init(),B.ssort(),B.geth();
        for(len=1;len<=n>>1;++len)
            for(i=len,j=len<<1;j<=n;i+=len,j+=len)
	    {
                x=min(A.LCP(i,j),len),y=min(B.LCP(n-i+2,n-j+2),len-1);
                if(x+y>=len) t=x+y-len+1,++st[i-y],--st[i-y+t],++ed[j+x-t],--ed[j+x];
            }
        for(i=1;i<=n;++i) st[i]+=st[i-1],ed[i]+=ed[i-1];
        for(ans=0,i=1;i<=n;++i) ans+=1ll*ed[i]*st[i+1];
        printf("%lld\n",ans);
    }
}

网格

Luogu
LOJ
UOJ
BZOJ
先特判掉答案为\(-1\)的情况,只有可能只剩一个格子,或者只剩两个相邻的格子。
接下来如果答案为\(0\),那么说明原图已经不连通了。
如果答案为\(1\),那么说明存在一个点把它删掉之后会使得图不连通(也就是割点)。
那么将一个点为中心的\(5\times5\)的网格的格子全部拿出来,跑一遍tarjin就行了。

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<cstring>
#include<utility>
#include<algorithm>
using pi=std::pair<int,int>;
const int N=2500007;
int n,m,c,tim,tot,ans,dfn[N],low[N],cut[N],is[N];std::map<pi,int>vis,id;
pi a[N],b[N];std::set<pi>s;std::map<pi,int>A,B;std::queue<pi>q;std::vector<int>e[N];
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
void add(int u,int v){e[u].push_back(v),e[u].push_back(v),e[v].push_back(u),e[v].push_back(u);}
void tarjan(int u)
{
    dfn[u]=low[u]=++tim;
    for(int v:e[u])
	if(!dfn[v])
	{
	    tarjan(v),low[u]=std::min(low[u],low[v]);
	    if(dfn[u]==low[v]) ++cut[u];
	}
	else low[u]=std::min(low[u],dfn[v]);
    if(((u!=1&&cut[u])||cut[u]>1)&&is[u]) ans=std::min(ans,1);
}
void bfs(pi x)
{
    tim=tot=0,q.push(x),vis[x]=1;
    while(!q.empty())
    {
	pi u=q.front();q.pop();
	for(int i=-2;i<=2;++i)
	    for(int j=-2;j<=2;++j)
	    {
		int x=u.first+i,y=u.second+j;pi v(x,y);
		if(x<1||x>n||y<1||y>m) continue;
		if(!s.count(v))
		{
		    if(!id[v]) id[v]=++tot,b[tot]=v;
		    if(std::max(abs(i),abs(j))<2) is[id[v]]=1;
		}
		else if(!vis[v]) q.push(v),vis[v]=1;
	    }
    }
    for(int i=1;i<=tot;++i)
    {
	pi x(b[i].first+1,b[i].second);if(id[x])add(i,id[x]);
	pi y(b[i].first,b[i].second+1);if(id[y])add(i,id[y]);
    }
    tarjan(1),memset(is+1,0,4*tot),memset(dfn+1,0,4*tot),memset(cut+1,0,4*tot),id.clear();
    for(int i=1;i<=tot;++i) e[i].clear();
    if(tim!=tot) ans=std::min(ans,0);
}
void solve()
{
    n=read(),m=read(),c=read(),ans=2,s.clear(),vis.clear();
    for(int i=1;i<=c;++i) a[i].first=read(),a[i].second=read(),s.insert(a[i]);
    if(1ll*n*m-c<2) return puts("-1"),void();
    for(int i=1;i<=c;++i) if(!vis[a[i]]) bfs(a[i]);
    if(std::min(n,m)==1) ans=std::min(ans,1);
    if(1ll*n*m-c==2&&ans) ans=-1;
    printf("%d\n",ans);
}
int main(){for(int t=read();t;--t)solve();}

循环之美

Luogu
LOJ
UOJ
BZOJ

\[\begin{aligned} ans=f(n,m,k)&=\sum\limits_{i=1}^n\sum\limits_{j=1}^m[i\perp j][j\perp k]\\ &=\sum\limits_{d|k}\mu(d)\sum\limits_{i=1}^n\sum\limits_{j=1}^{\lfloor\frac md\rfloor}[i\perp dj]\\ &=\sum\limits_{d|k}\mu(d)\sum\limits_{i=1}^n\sum\limits_{j=1}^{\lfloor\frac md\rfloor}[i\perp d][i\perp j]\\ &=\sum\limits_{d|k}\mu(d)f(\frac md,n,d) \end{aligned} \]

\[f(n,m,1)=\sum\limits_{d=1}^n\mu(d)\lfloor\frac nd\rfloor\lfloor\frac md\rfloor \]

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e7+7;
struct node{int n,m,k;};
int operator<(node a,node b){return a.n==b.n? (a.m==b.m? a.k<b.k:a.m<b.m):a.n<b.n;};
int read(){int x;cin>>x;return x;}
int lim,num[2000],flag[N],prime[N],mu[N],mus[N];
map<int,int>mp;
map<node,LL>Ans;
int smu(int x)
{
    if(x<=lim) return mus[x];
    if(mp[x]) return mp[x];
    int sum=1,i=2,j=sqrt(x);
    for(;i<=j;++i) sum-=smu(x/i);
    for(;i<=x;i=j+1) j=x/(x/i),sum-=(j-i+1)*smu(x/i);
    return mp[x]=sum;
}
LL solve(int n,int m,int k)
{
    if(!n||!m) return 0;
    node tmp=node{n,m,k};
    LL sum=0;
    if(Ans[tmp])return Ans[tmp];
    if(k==1)
    {
        if(n>m) swap(n,m);
        int i,j,now,last;
        for(i=1,last=0;i<=n;i=j+1,last=now) j=min(n/(n/i),m/(m/i)),now=smu(j),sum+=1ll*(n/i)*(m/i)*(now-last);
        Ans[node{m,n,k}]=sum;
    }
    else for(int i=1;i<=num[0]&&num[i]<=k;++i) if(k%num[i]==0&&mu[num[i]]) sum+=solve(m/num[i],n,num[i])*mu[num[i]];
    return Ans[tmp]=sum;
}
int main()
{
    int n=read(),m=read(),k=read(),i,j;lim=min(10000000,max(k,min(n,m))),mu[1]=1;
    for(i=2;i<=lim;++i)
    {
        if(!flag[i]) prime[++prime[0]]=i,mu[i]=-1;
        for(j=1;j<=prime[0]&&i*prime[j]<=lim;++j)
        {
            flag[i*prime[j]]=1;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i]; else{mu[i*prime[j]]=0;break;}
        }
    }
    for(i=1;i<=lim;++i) mus[i]=mus[i-1]+mu[i];
    for(i=1;i<=k;++i) if(!(k%i)) num[++num[0]]=i;
    return !printf("%lld",solve(n,m,k));
}

区间

Luogu
LOJ
UOJ
BZOJ
双指针+线段树。

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
    char ibuf[(1<<21)+1],*iS,*iT;
    char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
    int read(){int x=0;char ch=Get();while(ch>'9'||ch<'0')ch=Get();while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=Get();return x;}
}
using namespace IO;
const int N=500001;
struct node{int l,r,w;}a[N];
bool operator<(node a,node b){return a.w<b.w;}
int Hash[N<<1],tag[N<<3],sum[N<<3];
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
void modify(int p,int x){sum[p]+=x,tag[p]+=x;}
void pushdown(int p){if(tag[p])modify(ls,tag[p]),modify(rs,tag[p]),tag[p]=0;}
void pushup(int p){sum[p]=max(sum[ls],sum[rs]);}
void update(int p,int l,int r,int L,int R,int x)
{
    if(L>r||l>R) return;
    if(L<=l&&r<=R) return modify(p,x);
    pushdown(p),update(ls,l,mid,L,R,x),update(rs,mid+1,r,L,R,x),pushup(p);
}
#undef ls
#undef rs
#undef mid
int main()
{
    int n=read(),m=read(),num,Min=2147483647,Max=0,head=0,tail=0,ans=2147483647;
    for(int i=1,tot=0;i<=n;++i) Hash[++tot]=a[i].l=read(),Hash[++tot]=a[i].r=read(),a[i].w=a[i].r-a[i].l;
    sort(Hash+1,Hash+(n<<1)+1),num=unique(Hash+1,Hash+(n<<1)+1)-(Hash+1),sort(a+1,a+n+1);
    for(int i=1;i<=n;++i) Min=min(Min,a[i].l=lower_bound(Hash+1,Hash+num+1,a[i].l)-Hash),Max=max(Max,a[i].r=lower_bound(Hash+1,Hash+num+1,a[i].r)-Hash);
    while(tail<n)
    {
	while(sum[1]<m&&tail<=n) ++tail,update(1,Min,Max,a[tail].l,a[tail].r,1);
	if(sum[1]<m) break;
	while(sum[1]>=m&&tail>=head) ++head,update(1,Min,Max,a[head].l,a[head].r,-1);
	ans=min(ans,a[tail].w-a[head].w);
    }
    if(ans==2147483647) ans=-1;
    return cout<<ans,0;
}

国王饮水记

Luogu
LOJ
UOJ
BZOJ
把所有城市按水位高度降序排序,那么在首都后面的城市可以忽略了。
首先如果没有限制,那么最优策略是每次找水位高度比首都高的最低的城市合并。
现在有了次数限制之后不能这么做了,但是我们知道合并过的城市一定是一段前缀。
\(f_{i,j}\)表示前\(j\)个城市合并了\(i\)次的最优答案。记\(sum_i\)为前\(i\)个城市的水位高度之和。
转移方程为\(f_{i,j}=\max(\frac{f(i-1,k)+(sum_j-sum_k)}{j-k+1})\),斜率优化即可。

#include<bits/stdc++.h>
#define P pair<int,double>
using namespace std;
int read(){int x;cin>>x;return x;}
const int N=8192;
int h[N],s[N],a[400],g[16][N],v[16],p;
double f[16][N];
P q[N],A,B;
double operator%(P a,P b){return (b.second-a.second)/(b.first-a.first);}
void div(int x)
{
    long long q=0;
    for(int i=0;i<=p;++i) q=q*1000000000+a[i],a[i]=q/x,q%=x;
}
int main()
{
    int k=read(),n,m=read(),x,i,j,l,hd,tl;p=read()/9+1;
    for(h[n=1]=read(),i=2;i<=k;++i) if((x=read())>h[1]) h[++n]=x;
    for(sort(h+1,h+n+1),m=min(n,m),i=1;i<=n;++i) f[0][i]=h[1],s[i]=s[i-1]+h[i];
    for(l=min(m,14),i=1;i<=l;++i)
        for(j=2,hd=1,tl=0;j<=n;++j)
	{
            A=P(j-2,s[j-1]-f[i-1][j-1]);
            while(hd<tl&&q[tl-1]%q[tl]>q[tl]%A) --tl;
            q[++tl]=A,B=P(j,s[j]);
            while(hd<tl&&q[hd]%B<q[hd+1]%B) ++hd;
            g[i][j]=q[hd].first+1,f[i][j]=q[hd]%B;
        }
    for(v[l]=n-m+l,i=l;i;--i) v[i-1]=g[i][v[i]];
    for(a[0]=h[1],i=1;i<=l;++i) a[0]+=s[v[i]]-s[v[i-1]],div(v[i]-v[i-1]+1);
    for(i=v[l]+1;i<=n;++i) a[0]+=h[i],div(2);
    for(printf("%d.",a[0]),i=1;i<=p;++i) printf("%09d",a[i]);
}