前言
- 比赛链接。
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\)(我不提是谁)。