前言
- 比赛链接。
T2 部分分给得特别足,\(60pts\),而且他不可能剩下的数据全放菊花,所以得到了 \(76pts\),但赛时想了很长时间正解,没有想出来,给后面题剩的时间不多,就都胡暴力了,\(T4\) 甚至忘了剪枝,剪完之后 \(20pts\to 60pts\) 没绷住。
说到这儿要吐槽一下 T4 数据水的离谱,什么高级复杂度的都放过去了(当然纯爆搜没放过去),在 CF 上第六个点就 T 了。
赛时有人 T2 的暴力菊花都卡不掉,但是没有 A,好多人造数据没卡掉,我不信邪啊,造了个万花筒(玫瑰花)数据给他卡了,jijidawang
甚至邀请 miaomiao
把这个加到数据里(鞭尸了属于是)。
hack 数据生成器
#include<bits/stdc++.h>
using namespace std;
signed main()
{freopen("/dev/urandom","r",stdin);freopen("rand.out","w",stdout);srand(getchar()*getchar()*getchar()*time(0));puts("1");int n=10000,m=40000;printf("%d %d\n",n,m);for(int i=2;i<=n;i++){int w=rand()%1000+1;printf("%d %d %d\n",1,i,w);}int t=0;for(int i=2;i<=n;i++)for(int j=i+1;j<=n;j++){t++;int w=rand()%1000+1;printf("%d %d %d\n",i,j,w);if(t+n-1>=m) return 0;}
}
T1 数字三角形
- 原题:Fillomino 2。
签到题,从后往前处理,能往下铺就往下铺,否则就往左铺,当然反过来也一样,可以证明其一定恰好铺完所有位置且联通。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=510;
template<typename Tp> inline void read(Tp&x)
{x=0;register bool z=true;register char c=getchar();for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,a[N],ans[N][N];
void dfs(int pos,int sum,int x,int y)
{if(sum==0) return ;ans[x][y]=pos;if(!ans[x+1][y]&&x+1<=n) dfs(pos,sum-1,x+1,y);else dfs(pos,sum-1,x,y-1);
}
signed main()
{read(n);for(int i=1;i<=n;i++) read(a[i]);for(int i=n;i>=1;i--) dfs(a[i],a[i],i,i);for(int i=1;i<=n;i++){for(int j=1;j<=i;j++)write(ans[i][j]),putchar(' ');puts("");}
}
T2 那一天她离我而去
-
部分分 \(23pts\):纯爆搜,能且只能过 \(n=m\) 的基环树数据。
-
部分分 \(76pts\):\(n=m\) 的按照上面处理,其他的枚举所有与 \(1\) 连边的点 \(x\) 并断开这条边跑最短路,答案为 \(dis_x+w(1,x)\),若用 dijkstra 复杂度为 \(O(nm\log n)\)。(好像不特判 \(n=m\) 也是这个分?数据太水。)
-
部分分 \(76pts\):有人是这么干的,爆搜加剪枝。
-
正解:
会这个正解前需要先会一个同样
甚至更暴力的算法,从与 \(1\) 相连的一个点 \(x\) 出发跑最短路,则答案为 \(\min\limits_{y\in son_1}\{dis_y+w(1,y)\}\),枚举这个 \(x\),跑完最短路后再枚举 \(y\),复杂度为 \(O(nm\log n+n^2)\)。考虑这么做多个 \(n^2\) 十分唐氏,于是建了个超级汇点,将与 \(1\) 相连的点抽出一部分与 \(1\) 断开去连接 \(n+1\), 边权和原来一致,那么 \(1\) 作为超级源点跑最短路,最后答案就是 \(dis_{n+1}\)。
想到这里正解就差不多了,需要满足任意两个节点存在一次二者不同时连接 \(1\) 或 \(n+1\),想到二进制拆分,将其赋予下标的值,那么任意两点用二进制表示定至少一位不同,那么对于第 \(i\) 位上为 \(1\) 的连接 \(1\),剩下的连接 \(n+1\),再跑最短路,做到了不重不漏。
复杂度 \(O(m\log^2 n)\)。
CuFeO4
有建最短路 dag 的单 \(\log\) 做法,但我不会 \(dag\)。以上复杂度均针对 dijkstra 且菊花图,至于最短路方面,由于没有卡 spfa,所以 spfa 实际更快。
点击查看代码
#include<bits/stdc++.h> #define ll long long #define endl '\n' #define sort stable_sort using namespace std; const int N=2e4+10,M=8e4+10; template<typename Tp> inline void read(Tp&x) {x=0;register bool z=true;register char c=getchar();for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);x=(z?x:~x+1); } template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);} template<typename Tp> inline void wt(Tp x) {if(x>9)wt(x/10);putchar((x%10)+'0');} template<typename Tp> inline void write(Tp x) {if(x<0)putchar('-'),x=~x+1;wt(x);} template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);} int T,n,m,cnt,ans,dis[N]; bool vis[N]; pair<int,int>pos[N]; int tot,head[N],to[M],nxt[M],w[M]; int last,_head[N],_to[M],_nxt[M],_w[M]; void add(int x,int y,int z) {nxt[++tot]=head[x];to[tot]=y;w[tot]=z;head[x]=tot; } void spfa(int s) {queue<int>q;memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));dis[s]=0,vis[s]=1,q.push(s);while(!q.empty()){int x=q.front();q.pop();vis[x]=0;for(int i=head[x];i;i=nxt[i]){int y=to[i],z=w[i];if(dis[y]>dis[x]+z){dis[y]=dis[x]+z;if(!vis[y]) {vis[y]=1;q.push(y);}}}} } signed main() {read(T);while(T--){memset(head,0,sizeof(head));memset(nxt,0,sizeof(nxt));memset(to,0,sizeof(to));memset(w,0,sizeof(w));cnt=tot=0,ans=0x3f3f3f3f;read(n,m);for(int i=1,x,y,z;i<=m;i++){read(x,y,z);if(x>y) swap(x,y);if(x==1) pos[++cnt]=make_pair(y,z);else add(x,y,z),add(y,x,z);}memcpy(_head,head,sizeof(head));memcpy(_nxt,nxt,sizeof(nxt));memcpy(_to,to,sizeof(to));memcpy(_w,w,sizeof(w));last=tot;for(int i=0;(1<<i)<=cnt;i++){memcpy(head,_head,sizeof(head));memcpy(nxt,_nxt,sizeof(nxt));memcpy(to,_to,sizeof(to));memcpy(w,_w,sizeof(w));tot=last;for(int j=1;j<=cnt;j++){if(j&(1<<i)) add(1,pos[j].first,pos[j].second);else add(pos[j].first,n+1,pos[j].second);}spfa(1);ans=min(ans,dis[n+1]);}write(ans>=0x3f3f3f3f?-1:ans),puts("");} }
T3 哪一天她能重回我身边
貌似可做,但又不太会,先隔着。
T4 单调区间
-
原题:Decinc Dividing。
-
其中的 \(check\) 部分还能单独弄出一个原题:Two Merged Sequences。
-
部分分 \(20pts\):纯爆搜。
-
部分分 \(60pts\):加个剪枝。
-
部分分 \(100pts\):考虑存在单调性,双指针跑,赛时
Pursuing_OIer
是这么写的,直接过了,(CF 过不去)。 -
正解:
不同于官方题解的 \(O(n)\) 带常熟做法,这是一个带 \(\log\) 的做法,但由于跑不满且前者带常数,跑出来没怎么慢。
在写正解之前我们需要一个复杂度更优秀的 \(check\),前面的 \(check\) 都是爆搜加剪枝的,复杂度玄学可卡。
这个 \(check\) 贺的官方题解了,是个 DP:
首先给出一个十分别扭的定义:
\(f_{i,0}\) 表示第 \(i\) 个元素在 递增 序列中时,递减 序列最后一个元素的 最大值;
\(f_{i,1}\) 表示第 \(i\) 个元素在 递减 序列中时,递增 序列最后一个元素的 最小值。
初始值有 \(f_{i,0}=0,f_{i,1}=n+1,f_{l,0}=n+1,f_{l,1}=0\)。
枚举第 \(i-1\) 个元素在递增序列还是递减序列。然后尝试更新 \(f_{i,0||1}\),是要 \(f_{r,0}\ne 0||f_{r,1}\ne n+1\) 就说明存在合法方案。
还可以顺便处理出以 \(i\) 为左端点,其合法的最远右端点,根据其单调性,下面有用。
check
void check(int x) {if(mx[x]) return ;f[x][0]=n+1,f[x][1]=0;for(int i=x+1;i<=n;i++){f[i][0]=0,f[i][1]=n+1;if(f[i-1][0]!=0)//a[i-1] 能在递增末尾{if(a[i]>a[i-1]) f[i][0]=max(f[i][0],f[i-1][0]);//a[i] 接在递增末尾if(a[i]<f[i-1][0]) f[i][1]=min(f[i][1],a[i-1]);//a[i] 接在递减末尾}if(f[i-1][1]!=n+1)//a[i-1] 能在递减末尾{if(a[i]<a[i-1]) f[i][1]=min(f[i][1],f[i-1][1]);//a[i] 接在递减末尾if(a[i]>f[i-1][1]) f[i][0]=max(f[i][0],a[i-1]);//a[i] 接在递增末尾}if(f[i][0]==0&&f[i][1]==n+1) {mx[x]=i-1; return ;}}mx[x]=n;//以 x 为左端点能到达最远的右端点 }
然后根据决策单调性,显然有对于任何 \(i\) 有 \(mx_{i}\ge mx_{i-1}\),那么对于一个区间 \([l,r]\),若 \(mx_{l}=mx_{r}\),则这一段区间的所有 \(mx_i\) 均为 \(mx_l\),根据此考虑分治,若不相等则递归 \([l,mid],[mid,r]\),这么做比递归 \([l,mid],[mid+1,r]\) 能少一个 \(check\),推荐这么写。
复杂度为 \(O(n\log n)\) 级别的,可以证明,但由于我的证明过程大差不差但不太可观,就不放出来了,
懒就直说。点击查看代码
#include<bits/stdc++.h> #define ll long long #define endl '\n' #define sort stable_sort using namespace std; const int N=2e5+10; template<typename Tp> inline void read(Tp&x) {x=0;register bool z=true;register char c=getchar();for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);x=(z?x:~x+1); } template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);} template<typename Tp> inline void wt(Tp x) {if(x>9)wt(x/10);putchar((x%10)+'0');} template<typename Tp> inline void write(Tp x) {if(x<0)putchar('-'),x=~x+1;wt(x);} template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);} int n,a[N],f[N][2],mx[N]; ll ans; void check(int x) {if(mx[x]) return ;f[x][0]=n+1,f[x][1]=0;for(int i=x+1;i<=n;i++){f[i][0]=0,f[i][1]=n+1;if(f[i-1][0]!=0){if(a[i]>a[i-1]) f[i][0]=max(f[i][0],f[i-1][0]);if(a[i]<f[i-1][0]) f[i][1]=min(f[i][1],a[i-1]);}if(f[i-1][1]!=n+1){if(a[i]<a[i-1]) f[i][1]=min(f[i][1],f[i-1][1]);if(a[i]>f[i-1][1]) f[i][0]=max(f[i][0],a[i-1]);}if(f[i][0]==0&&f[i][1]==n+1) {mx[x]=i-1; return ;}}mx[x]=n; } void solve(int l,int r) {if(l>=r-1) return ;if(mx[l]==mx[r]) for(int i=l;i<=r;i++) mx[i]=mx[l];else {int mid=(l+r)>>1;check(mid);solve(l,mid),solve(mid,r);} } signed main() {read(n);for(int i=1;i<=n;i++) read(a[i]);check(1),check(n);solve(1,n);for(int i=1;i<=n;i++) ans+=mx[i]-i+1;write(ans); }
总结
感觉……没啥可总结的,最近心情不是特别好但没有特别差,主要是因为今天打的还凑合,不然心态又要炸了。
除了 T4 忘了剪枝别的都挺好的,那个 T2、T3 正解看起来就不像我赛时能想出来的。
附录
-
今天 T2、T3 选自往届模拟赛【失恋三部曲】(还有一个没放进来),然后昨天是七夕节,今天放这个题目有理由怀疑是故意的(反正不关我事)。
-
关于 CF 把所有爬都 ban 了,现在交 CF 的题只能上 CF 交了(vjudge 都不行)。
-
本来所今天没模拟赛的,到了晚上快放学了才把时间改了,以后不要这么做了谢谢(我没什么损失,但貌似好多人都准备今天改题呢),但明天确确实实没有模拟赛,课件都放了。
-
每天中午起床来机房都和渡劫一样,又困又热离得还远还要抓紧时间不迟到。
-
今天属于剪枝专场,优秀的剪枝可以给一个纯爆搜的同志加高达 \(84pts\)(我不提是谁)。