[考試反思]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 }
View Code

 

 

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 }
View Code

 

 

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 }
View Code

 

不難發現,我們再每一次重造矩陣的時候,其實只有一行是不一樣的,就是你單獨考慮的$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 }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM