T1:
求出前缀和,三维偏序O(nlogn)CDQ
二维其实就可以
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1e6+10,inf=2147483; int n,ans,tot,tree[2*N]; long long a[N],b[N]; long long suma[N],sumb[N],c[2*N]; struct node{ int ha,hb,id; }s[N],d[N]; void add(int x,int k){ for(;x<=tot;x+=(x&-x))tree[x]=min(tree[x],k); } void clear(int x){ for(;x<=tot;x+=(x&-x))tree[x]=inf; } int ask(int x){ int num=inf; for(;x;x-=(x&-x))num=min(tree[x],num); return num; } void work(int l,int r){ if(l==r){ return; } int mid=(l+r)/2; work(l,mid),work(mid+1,r); int ll=l,rr=mid+1,p=l; while(ll<=mid&&rr<=r){ if(s[ll].ha<=s[rr].ha){ add(s[ll].hb,s[ll].id); d[p++]=s[ll++]; } else{ int x=ask(s[rr].hb); if(x!=inf)ans=max(ans,s[rr].id-x); d[p++]=s[rr++]; } } while(rr<=r){ int x=ask(s[rr].hb); if(x!=inf)ans=max(ans,s[rr].id-x); d[p++]=s[rr++]; } for(int i=l;i<=ll;i++){ clear(s[i].hb); } while(ll<=mid)d[p++]=s[ll++]; for(int i=l;i<=r;i++)s[i]=d[i]; } int main(){ // freopen("sequence.in","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } for(int i=1;i<=n;i++){ scanf("%lld",&b[i]); } for(int i=0;i<=n;i++){ if(i)suma[i]=suma[i-1]+a[i]; if(i)sumb[i]=sumb[i-1]+b[i]; c[++tot]=suma[i],c[++tot]=sumb[i]; } sort(c+1,c+tot+1); tot=unique(c+1,c+tot+1)-c-1; for(int i=0;i<=tot;i++)tree[i]=inf; for(int i=0;i<=n;i++){ s[i].ha=lower_bound(c+1,c+tot+1,suma[i])-c; s[i].hb=lower_bound(c+1,c+tot+1,sumb[i])-c; s[i].id=i; } work(0,n); printf("%d\n",ans); return 0; }
一开始没有处理0的位置,出题人慷慨地送了我90pts
T2:
区间DP+四边形不等式优化
DP合并方式是枚举决策点,左右可以看作独立的树,然后再整体加一层的贡献
发现决策点单调,于是f[i][j]只从p[i][j-1]->p[i+1][j]枚举决策点。p为决策点数组。
跳过四边形不等式证明:如果觉得决策点单调就把决策点矩阵打印出来,发现行列上均单调,则有可能可以四边形不等式优化。
O(n)
#include<iostream> #include<cstdio> using namespace std; const long long inf=1e18; int n,a[5010],p[5010][5010]; long long f[5010][5010],sum[5010]; int main() { // freopen("tree.in","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); sum[i]=sum[i-1]+a[i]; } for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ f[i][j]=inf; } } for(int i=1;i<=n;i++)f[i][i]=a[i],p[i][i]=i; for(int i=2;i<=n;i++){ for(int j=1;j+i-1<=n;j++){ for(int k=p[j][j+i-2];k<=p[j+1][j+i-1];k++){ if(f[j][k-1]+f[k+1][j+i-1]+sum[j+i-1]-sum[j-1]<f[j][j+i-1]){ f[j][j+i-1]=f[j][k-1]+f[k+1][j+i-1]+sum[j+i-1]-sum[j-1]; p[j][j+i-1]=k; } } } } printf("%lld\n",f[1][n]); return 0; }
T3:
设从k点开始转移,以1的最终值为答案。
列出转移方程:f[i]=∑f[j]/x+1,x为i的出度。移项,列出高斯消元数组。
对于一个点,当它作为k时,它的f值为0。又发现对于不同的k,每次其它点的消元式子并不会发生变化。
线段树分治,每次存下当前的数组,只消元一半,然后递归进下一层。当处理到只有一个点的区间时,这个点的答案即为消元数组中1号点对应的答案。
一开始避免了消元时的选行,以及消元的时候出现0似乎没有什么影响:
#include<iostream> #include<cstdio> using namespace std; const int mod=998244353; int n,m; int ver[100010],Next[100010],head[310],tot,chu[310]; long long val[310][310],ans[310],d[310][310][310]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } long long ks(long long x,int k){ long long num=1; while(k){ if(k&1)num=num*x%mod; x=x*x%mod; k>>=1; } return num; } void make(int l,int r){ // int pos; for(int i=l;i<=r;i++){ // pos=i; // for(int j=i;j<=n;j++){ // if(val[j][i]>val[pos][i])pos=j; // } // for(int j=1;j<=n+1;j++){ // swap(val[i][j],val[pos][j]); // } int tmp=val[i][i]; if(!tmp)continue; long long inv=ks(tmp,mod-2); for(int j=1;j<=n+1;j++){ val[i][j]=val[i][j]*inv%mod; } for(int j=1;j<=n;j++){ if(j!=i){ int s=val[j][i]; for(int k=1;k<=n+1;k++){ val[j][k]=(val[j][k]-s*val[i][k]%mod+mod)%mod; } } } } } void work(int l,int r,int dep){ if(l==r){ ans[l]=val[1][n+1]; return; } // long long d[310][310]; for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++){ d[dep][i][j]=val[i][j]; } } int mid=(l+r)/2; make(l,mid); work(mid+1,r,dep+1); for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++){ val[i][j]=d[dep][i][j]; } } make(mid+1,r); work(l,mid,dep+1); } int main() { // freopen("walk.in","r",stdin); // freopen("1.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y); chu[x]++; } for(int i=1;i<=n;i++){ val[i][i]=-chu[i]; // long long inv=ks(chu[i],mod-2); for(int j=head[i];j;j=Next[j]){ int y=ver[j]; val[i][y]++; } val[i][n+1]=-chu[i]; } work(1,n,1); for(int i=2;i<=n;i++){ printf("%lld\n",ans[i]); } return 0; }
这样选行会错的原因是,对于一个k来说,整个消元过程中它是不能被选择到的。在它作为k的意义下,它的数组其实相当于不存在。
及时跳出选行即可:
#include<iostream> #include<cstdio> using namespace std; const int mod=998244353; int n,m; int ver[100010],Next[100010],head[310],tot,chu[310]; long long val[310][310],ans[310],d[310][310][310]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } long long ks(long long x,int k){ long long num=1; while(k){ if(k&1)num=num*x%mod; x=x*x%mod; k>>=1; } return num; } void make(int l,int r){ int pos; for(int i=l;i<=r;i++){ pos=i; for(int j=i;j<=n;j++){ if(val[j][i]){pos=j;break;} } for(int j=1;j<=n+1;j++){ swap(val[i][j],val[pos][j]); } for(int j=1;j<=n+1;j++)val[i][j]=(val[i][j]+mod)%mod; int tmp=val[i][i]; if(!tmp)continue; long long inv=ks(tmp,mod-2); for(int j=1;j<=n+1;j++){ val[i][j]=val[i][j]*inv%mod; } for(int j=1;j<=n;j++){ if(j!=i){ int s=val[j][i]; for(int k=1;k<=n+1;k++){ val[j][k]=(val[j][k]-s*val[i][k]%mod+mod)%mod; } } } } } void work(int l,int r,int dep){ if(l==r){ ans[l]=val[1][n+1]; return; } // long long d[310][310]; for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++){ d[dep][i][j]=val[i][j]; } } int mid=(l+r)/2; make(l,mid); work(mid+1,r,dep+1); for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++){ val[i][j]=d[dep][i][j]; } } make(mid+1,r); work(l,mid,dep+1); } int main() { // freopen("walk.in","r",stdin); // freopen("1.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y); chu[x]++; } for(int i=1;i<=n;i++){ val[i][i]=-chu[i]; // long long inv=ks(chu[i],mod-2); for(int j=head[i];j;j=Next[j]){ int y=ver[j]; val[i][y]++; } val[i][n+1]=-chu[i]; } work(1,n,1); for(int i=2;i<=n;i++){ printf("%lld\n",ans[i]); } return 0; }