[考试反思]1029csp-s模拟测试93:殇逝
停止!
思考,
回顾。
疑惑?
遗忘…
一直只是在匆忙的赶进度,实际上的确是一点也不扎实。
T1,裸的偏序,想了一个多小时什么也没想到,只打了$O(n^2)$
难道之前学的就这么白学了?
T2,简单dp,没有任何优化就扔了。
单调性优化做了无数次还是不会。
T3,暴力和部分分,部分分写错,暴力没取模。
90多场了啊,这些毛病还在犯,那么考试还有什么意义啊?
需要真正的反思与总结了,需要真正内化成自己的东西,才不是浑浑噩噩度日吧。
少说点废话吧。
T1:序列
求区间大于0,其实就是前缀和之后求$b[j] \geq b[i]$且$a[j] \geq a[i]$的最大$j-i$
看起来向一个三维偏序,但是题目保证有大于0的解,所以当$i \geq j$时并不会更新答案。
所以其实$i/j$这一维并不需要偏序,只用考虑$a/b$两个数组即可。
而$i/j$只是转移值,现在转化为二维偏序问题。
乍一下想动用各种数据结构,但是内存都开不下。
而偏序问题的一个技巧就是通过排序某一维来取消这一维的限制。所以维数-1。
然后就是一个一维偏序问题了,树状数组维护即可。
总的来说就是按$a$排序,将$b$离散化,树状数组维护$i/j$下标最小值即可。
1 #include<cstdio> 2 #include<algorithm> 3 #include<map> 4 using namespace std; 5 #define ll long long 6 map<ll,int>M; 7 struct P{ 8 ll a;int b,p; 9 friend bool operator<(P x,P y){ 10 return x.a<y.a||(x.a==y.a&&x.p<y.p); 11 } 12 }p[500005]; 13 long long a[500005],b[500005];int n,ans,cnt,t[500005]; 14 int read(){ 15 register int p=0,nt=0;register char ch=getchar(); 16 while(ch<'0'||ch>'9')nt=ch=='-',ch=getchar(); 17 while(ch>='0'&&ch<='9')p=(p<<3)+(p<<1)+ch-'0',ch=getchar(); 18 return nt?-p:p; 19 } 20 void set(int p,int w){for(;p<=500000;p+=p&-p)t[p]=min(t[p],w);} 21 int ask(int p,int w=500001){for(;p;p^=p&-p)w=min(w,t[p]);return w;} 22 int main(){//freopen("sequence.in","r",stdin); 23 n=read();M[0]; 24 for(int i=1;i<=n;++i)a[i]=a[i-1]+read(); 25 for(int i=1;i<=n;++i)b[i]=b[i-1]+read(),M[b[i]]; 26 for(map<ll,int>::iterator it=M.begin();it!=M.end();it++)(*it).second=++cnt; 27 for(int i=0;i<=n;++i)p[i]=(P){a[i],M[b[i]],i}; 28 sort(p,p+1+n); 29 for(int i=0;i<=500000;++i)t[i]=500001; 30 for(int i=0;i<=n;++i)ans=max(ans,p[i].p-ask(p[i].b)),set(p[i].b,p[i].p); 31 printf("%d\n",ans); 32 }
T2:二叉搜索树
简单的$dp$就是$dp[i][j]$表示区间$[i,j]$之间的点形成树的代价,转移很简单:
$dp[i][j]=min(dp[i][k-1]+dp[k][j]+x[j]-x[i-1])$
其中x是权值的前缀和。
考虑优化:当你在某一个区间右端添加一个点的时候,你最后选定的根节点一定不会左移。
同理,你在左段添加一个点的话,最后选定的最优根节点也一定不会左移。
设$rt[i][j]$表示区间$[i,j]$的最优根节点。那么有
$rt[i][j-1] \leq rt[i][j] \leq rt[i+1][j]$
所以就可以dp了,用上面这个限制一下左右端点。
因为是单调的,所以在$n$种长度里每个位置平均只会被扫$O(n)$次。
所以总的复杂度是$O(n^2)$
1 #include<cstdio> 2 int n;long long x[5005],dp[5005][5005],rt[5005][5005]; 3 main(){ 4 scanf("%d",&n); 5 for(int i=1;i<=n;++i)scanf("%lld",&x[i]),x[i]+=x[i-1]; 6 for(int l=1;l<=n;++l)for(int r=l;r<=n;++r)dp[l][r]=100000000000000000; 7 for(int l=1;l<=n;++l)dp[l][l]=x[l]-x[l-1],rt[l][l]=l; 8 for(int L=1;L<n;++L)for(int l=1,r=l+L;r<=n;++l,++r) 9 for(int m=rt[l][l+L-1];m<=rt[r-L+1][r];++m) 10 if(dp[l][m-1]+dp[m+1][r]+x[r]-x[l-1]<dp[l][r]) 11 dp[l][r]=dp[l][m-1]+dp[m+1][r]+x[r]-x[l-1],rt[l][r]=m; 12 printf("%lld\n",dp[1][n]); 13 }
T3:走路
题面差评。非得让我栽$n-1$个跟头干啥???做个题结果就真栽跟头了。
挺神仙的一道题。这种分治思想很巧妙。
之前有一道题好像叫《$Dash \ Speed$》是线段树结构的分治,这个也差不多。。。
我们考虑暴力做法。
因为我对期望的理解直到今天$LNC$好好地给我来了一遍才深刻一点。
因为正着推的时候它的转移概率之和不一定为1,而期望的实际含义则是加权平均数。
所以正着推的话要想得到期望就还得一直带着概率算,在出环的图里不可做。
所以要倒着推:设$f_i$表示期望再经过几步才能到$k$点。
转移就是$f_i=\sum\limits_{i \to j}\frac{f_j}{degree_i}$
$degree_i$表是i的出度。
这样的话转移的总概率就是每一条出边累加$\frac{1}{degree_i}$那么总概率当然是1。
而正着的式子却不是,不再赘述。
暴力的思路就是拆掉每个点的所有出边,然后做一次瓜丝消元$f_1$就是解。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define mod 998244353 5 #define ll long long 6 int n,m,cnt[305][305],deg[305],spj1=1,spj2=1;ll x[305][306],inv[100005],escape[305]; 7 ll pow(ll b,int t=mod-2,ll a=1){for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;} 8 void Gauss(){ 9 for(int i=1;i<=n;++i){ 10 if(!x[i][i])for(int j=i+1;j<=n;++j)if(x[j][i])swap(x[i],x[j]); 11 int inv=pow(x[i][i]); 12 for(int j=i;j<=n+1;++j)x[i][j]=x[i][j]*inv%mod; 13 for(int j=i+1;j<=n;++j)for(int k=n+1;k>=i;--k)x[j][k]=(x[j][k]-x[i][k]*x[j][i]%mod+mod)%mod; 14 } 15 for(int i=n;i;--i)for(int j=i-1;j;--j)x[j][n+1]=(x[j][n+1]-x[j][i]*x[i][n+1]%mod+mod)%mod; 16 } 17 main(){ 18 scanf("%d%d",&n,&m); 19 for(int i=1,X,Y;i<=m;++i){ 20 scanf("%d%d",&X,&Y),deg[X]++,cnt[X][Y]++; 21 if(i!=m&&X>Y)spj1=0; 22 if(X!=Y&&X!=1&&Y!=1)spj2=0; 23 } 24 for(int i=1;i<=m;++i)inv[i]=pow(i); 25 for(int A=2;A<=n;++A){ 26 for(int i=1;i<=n;++i)for(int j=1;j<=n+1;++j)x[i][j]=0; 27 for(int i=1;i<=n;++i)if(A!=i)for(int j=1;j<=n;++j)x[j][i]=cnt[i][j]*inv[deg[i]]%mod; 28 for(int i=1;i<=n;++i)x[i][i]--;x[1][n+1]=-1; 29 //for(int i=1;i<=n;++i,puts(""))for(int j=1;j<=n+1;++j)printf("%lld ",x[i][j]); 30 Gauss();for(int i=1;i<=n;++i)if(i!=A)x[A][n+1]+=x[i][n+1]; 31 printf("%lld\n",x[A][n+1]%mod-1); 32 } 33 }
不难发现,我们再每一次重造矩阵的时候,其实只有一行是不一样的,就是你单独考虑的$k$那一行。
考虑这个问题:我在瓜丝消元的时候填了一行,然后这一行一直在被消来消去,但是我们始终不用这一行来消其它的行。
那么最后求出的解当然是不会变的对吧?
所以我们先不拆任何边,这样建出一个矩阵。
然后我们对于目前要处理出的k,把这一行看成我们乱加的那一行,这行怎么被消都没有关系,但是我们不能用这一行去消其它行。
这样的话就相当与我们没有加这一堆边,它们对答案没有影响。
考虑递归解决,$solve(l,r)$表示我们正在处理$[l,r]$之内的k。
那么如果k在$[l,mid]$里面,那么$[mid+1,r]$里面的所有边都要生效,就是这些行都要被当作主元来给其它行消元。
消完之后我们就可以递归求解$solve(l,mid)$了。
这样的话如果我们可以发现,$solve(l,r)$的时候其实除了$[l,r]$以外的区间都被当成主元消过了。
那么如果我们递归到了某一个叶节点,那么就是除了这一行以外所有的行都被消过了。
这就和我上面说的情况一致了,这样就可以解出当前点的答案了。
当然如果我们要求解$solve(mid+1,r)$时,我们就需要用$[l,mid]$的边消元。
在这之前我们首先需要把矩阵还原,开数组存一下就好了。
为了方便,我们在每一次消元的时候,并不是把它消成上三角矩阵,而是消成单位矩阵,这样在后面方程比较好解,不用回代。
要注意:如果你的代码递归处函数出现了2个$n$级别的循环而不是$[l,r]$的循环,那么你的复杂度就是$O(n^3 \ log \ n)$了。并不是正解。
而如果只有一个$n$级别的循环,那么你的复杂度就是$O(n^3)$
证明稍后写吧。
问题就在于你的迭代式是下面的哪个:
$T(n)=Nn^2+2T(\frac{n}{2})$
$T(n)=N^2n+2T(\frac{n}{2})$
其中N是指题目输入的N,而n是指当前递归区间的大小。
当然在计算复杂度的时候可以把$N$直接提出,那么第一个式子就是:
$T(n)=N \times t(n) $
$t(n)=n^2+2t(\frac{n}{2})$
根据主定理,得到$t(n)=n^2$
所以$T(n)=n^3$
而对于第二个式子,其复杂度为:
$T(n)=N^2 \times t(n)$
$t(n)=n+2t(\frac{n}{2})$
这是一个非常常见的分治复杂度了。根据常识或者根据主定理,$t(n)=n\ log \ n $
所以$T(n)=n^3 \ log \ n$
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define mod 998244353 5 #define int long long 6 int n,m,cnt[305][305],deg[305],x[305][306],inv[100005],ans[305]; 7 int pow(int b,int t=mod-2,int a=1){for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;} 8 void Gauss(int l,int r,int L,int R){ 9 for(int i=L;i<=R;++i){ 10 int inv=pow(x[i][i]); 11 for(int j=l;j<=r;++j)x[i][j]=x[i][j]*inv%mod;x[i][0]=x[i][0]*inv%mod; 12 for(int j=1;j<=n;++j)if(i!=j){ 13 int t=x[j][i]; 14 for(int k=l;k<=r;++k)x[j][k]=(x[j][k]-x[i][k]*t%mod+mod)%mod; 15 x[j][0]=(x[j][0]-x[i][0]*t%mod+mod)%mod; 16 } 17 } 18 } 19 void solve(int l,int r){ 20 if(l==r)return ans[l]=(mod+x[1][0])%mod,(void)0; 21 int m=l+r>>1;int re[305][305]; 22 for(int i=1;i<=n;++i){for(int j=l;j<=r;++j)re[i][j]=x[i][j];re[i][0]=x[i][0];} 23 Gauss(l,r,l,m);solve(m+1,r); 24 for(int i=1;i<=n;++i){for(int j=l;j<=r;++j)x[i][j]=re[i][j];x[i][0]=re[i][0];} 25 Gauss(l,r,m+1,r);solve(l,m); 26 for(int i=1;i<=n;++i){for(int j=l;j<=r;++j)x[i][j]=re[i][j];x[i][0]=re[i][0];} 27 } 28 main(){ 29 scanf("%lld%lld",&n,&m); 30 for(int i=1,X,Y;i<=m;++i)scanf("%lld%lld",&X,&Y),deg[X]++,cnt[X][Y]++; 31 for(int i=1;i<=m;++i)inv[i]=pow(i); 32 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)x[i][j]=cnt[i][j]*inv[deg[i]]%mod; 33 for(int i=1;i<=n;++i)x[i][i]--,x[i][0]=-1; 34 solve(1,n);for(int i=2;i<=n;++i)printf("%lld\n",ans[i]); 35 }