「考試總結2020-08-11」省略
T1
地精部落
定義 \(f_{i,j}\) 為當前數集大小為 i,尾數在相對大小關係中的排序為 j
轉移的時候考慮在後面新增一個數字,在當前排列中排序為 k
如果滿足,那麼可以轉移到 \(f_{i+1,k} (k\in \{1,i+1\})\)
然後觀察哪些位置可以被轉移
對於一個 \(f_{i,j}\) 可以對大於等於它的數字造成貢獻
所以反過來,我們維護字首和就好了
#include<bits/stdc++.h> using namespace std; #define int long long #define For(i,a,b) for(register int i=a;i<=b;++i) namespace yspm{ inline int read() { int res=0,f=1; char k; while(!isdigit(k=getchar())) if(k=='-') f=-1; while(isdigit(k)) res=res*10+k-'0',k=getchar(); return res*f; } const int N=4210; int n,mod,f[N][N],sum[N],ans; inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;} inline int del(int x,int y){return x-y<0?x-y+mod:x-y;} signed main() { n=read(); mod=read(); f[1][1]=1; sum[1]=1; For(i,2,n) { if(i&1) For(j,1,i-1) f[i][j]=del(sum[i-1],sum[j-1]); else For(j,2,i) f[i][j]=del(sum[j-1],sum[0]); sum[0]=0; For(j,1,i) sum[j]=add(sum[j-1],f[i][j]); } For(i,1,n) ans=add(ans,f[n][i]); memset(f,0,sizeof(f)); f[1][1]=1; sum[1]=1; For(i,2,n) { if(!(i&1)) For(j,1,i-1) f[i][j]=del(sum[i-1],sum[j-1]); else For(j,2,i) f[i][j]=del(sum[j-1],sum[0]); sum[0]=0; For(j,1,i) sum[j]=add(sum[j-1],f[i][j]); } For(i,1,n) ans=add(ans,f[n][i]); cout<<ans<<endl; return 0; } } signed main(){return yspm::main();}
T2
\(bzoj4321\)
首先按照原來的套路觀察插入每個點的時候當前的序列的狀態,然後插空
下面定義衝突為:\(abs(p_i-p_{i+1})=1\)
那麼則有三種:
1.插空之後添加了
這裡是把那個 i 插入到 i-1 後面或者前面了,同時不是 i-1 和 i-2 相連的那個位置
2.插空之後衝突的個數不變
原來 i-1 和 i-2 相連,然後把 i 插入到這兩個中間
3.插空之後衝突的個數減少了
上面的情況,有 \(j/j-1\) 個空可以插進去
然後我們發現僅僅定義 \(f_{i,j}\) 表示i個數字,j個衝突是沒法轉移的
所以新增一維:\(f_{i,j,0/1}\) 表示 i 和 i-1 是不是相鄰
對應狀態進行轉移即可
最後答案:\(f_{n,0,0}\)
注意:這裡考慮往下一層轉移會相對好寫一點
T3
\(Luogu4309 [TJOI2013]\)最長上升子序列
這題 \(O(n^2 log n)\) 的線上做法比較簡單
但是並不能通過
所以我們離線
先用vector模擬出來最終的序列,然後對它做 LIS
在這個過程中,我們考慮對於每個棧中的元素存一個vector
用 vector 中最小的那個元素做二分時候的代表
如果不開
#define int long long
那麼vector過10萬
(所以考場掛了30)
然後是正解部分:
1.可以考慮平衡樹直接維護出來整個序列,不需要 \(vector\)
2.後面的查詢答案的部分可以考慮一種 \(O(n \log n)\) 的做法
用樹狀陣列維護最大值(注意,只能是有限制的一部分的最大值,不能差分……)
考慮離線下來的序列每個數當作結尾的 \(LIS\)
對於每個數字,先查詢比它小的數字中的 \(LIS\) 的最大值,然後加上1,再扔到樹狀數組裡面
最後 \(ans_i=max(ans_i,ans_{i-1})\)
然後就做完了
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define For(i,a,b) for(register int i=a;i<=b;++i)
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=1e5+10;
vector<int> las;
int n,id[N],pos,ans[N];
struct node{
int c[N];
inline int lowbit(int x){return x&(-x);}
inline void update(int x,int v)
{
for(;x<=n;x+=lowbit(x)) c[x]=max(c[x],v);
return ;
}
inline int query(int x)
{
int res=0;
for(;x;x-=lowbit(x)) res=max(res,c[x]);
return res;
}
}T;
signed main()
{
n=read();
For(i,1,n) pos=read(),las.insert(las.begin()+pos,i);
For(i,0,n-1) ans[las[i]]=T.query(las[i])+1,T.update(las[i],ans[las[i]]);
For(i,1,n) ans[i]=max(ans[i],ans[i-1]);
For(i,1,n) printf("%lld\n",ans[i]);
return 0;
}
}
signed main(){return yspm::main();}
T4
Luogu3320 SDOI2015尋寶遊戲
考試的時候沒時間想這題了,其實發現了總的路徑長度是個二倍關係的相關性質
本質上面這題是個虛樹的總邊長的二倍?補習虛樹去了……
然後是正經題解:
先思考總的路徑怎麼走最短,瞎走顯然是不能做到最優解的
一種做法是按照 \(dfn\) 走,正確性?因為不會走重複的反向
(反正我這麼著就理解了,但是如果理解不了的話可以考慮畫畫圖)
然後用一種資料結構維護 \(dfn\) 的大小
每次如果新增一個點的話,那麼要做的是把兩邊的距離斷開,然後新增上 \(l\to now\) 和 \(now\to r\),刪掉\(l\to r\)
刪除的話就是把 \(l\to r\) 加上,刪掉那倆
找 \(l,r\) 用二分,邊界需要特判一下
其實實現比較簡單
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define For(i,a,b) for(int i=a;i<=b;++i)
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res;
}
const int N=1e5+10;
struct node{
int to,dis,nxt;
}e[N<<1];
int head[N],cnt,dep[N],fa[N][20],dis[N],n,T,dfn[N],num,p[N],ans,l,r;
bool vis[N];
set<int> s;
inline void add(int u,int v,int w)
{
e[++cnt].to=v; e[cnt].dis=w; e[cnt].nxt=head[u];
return head[u]=cnt,void();
}
inline void dfs(int x,int fat)
{
dep[x]=dep[fat]+1; fa[x][0]=fat; dfn[x]=++num; p[num]=x;
for(int i=1;(1<<i)<=dep[x];++i) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=fat) dis[e[i].to]=dis[x]+e[i].dis,dfs(e[i].to,x);
return ;
}
inline int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;--i) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=19;i>=0;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline int dist(int x,int y)
{
int lc=lca(x,y);
return dis[x]+dis[y]-dis[lc]*2;
}
signed main()
{
n=read(); T=read();
for(int i=1,u,v,w;i<n;++i) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w); dfs(1,0);
while(T--)
{
int x=read();
if(!vis[x]) s.insert(dfn[x]);
x=dfn[x];
l=s.lower_bound(x)==s.begin()?*(--s.end()):*(--s.lower_bound(x)); l=p[l];
r=s.upper_bound(x)==s.end()?*s.begin():*s.upper_bound(x); r=p[r];
x=p[x];
if(vis[x]) s.erase(dfn[x]);
int tmp=dist(l,x)+dist(x,r)-dist(l,r);
if(vis[x]) ans-=tmp; else ans+=tmp; vis[x]=!vis[x];
cout<<ans<<endl;
}
return 0;
}
}
signed main(){return yspm::main();}
隨想
早上和媽媽聊了挺長時間的
感覺其實自己給自己壓力太大了,而且自己總是在思考自己面對了多麼多麼大的壓力
然後總是在注意別人在幹什麼,忘記自己其實應該改題,做題,反思
考試其實收穫挺大的,比如一些原來不敢想的 \(dp\) 其實現在也逼著自己去想,然後發現自己也能過掉的時候還真的是挺有成就感的一個事情
發現自己也能學會克魯斯卡爾重構樹然後過掉 \(IOI\) 題的時候也是相當舒服(雖然那題原來知道了後面的部分可以用二維數點解決)
原來自己其實是喜歡寫題解的,還特別喜歡把演算法的每個步驟都描述得相當清楚,然後讓看到的人都能看懂
也許是現在才剛剛發現自己過去的十幾二十天過得有點功利了吧
光顧著追求學會一些奇奇怪怪的東西顯得自己有多麼強
直到自己考場現推二項式反演的時候才知道學得其實一點也沒學懂
別人學得快,自己可以跟
但是當發現那真的不適合自己的時候也應該重新拾起來自己的節奏,慢慢把自己的題目改過來,把自己的程式碼實現好
該想到的東西慢慢想,總能想到的不是嗎?
該得到的東西慢慢來,總能得到的不是嗎?
考試忽高忽低,其實真正應該意識到的是以後晚上就不要想那麼多,想今天別人多比自己做了幾個題目
應該思考的是自己收穫到的是哪些,以後陷入定式思維之後要及時跳出來,然後再換個角度分析它的本質
就像模擬退火一樣吧,多谷函式不是跳到一個坑裡面那就一定能遇到最小值吧