OI 笑传 #4
我咋什么都不会啊。
赛时
还是先去问候大样例,我草怎么给了这么多,这下再挂分就没理由了哥们。
外面下雨了。
T1 小思维,观察了一会最后式子的形式发现差分一下除了第一位其它位上应该都是个非负定值,最小更改次数可以认为是差分之后出现最多的数减去总长度。
但是发现直接对原数组差分会受前面数的影响就把规律打乱了,于是改成直接对第一个数做差,再除以下标减一的形式,这样就好判断了,差值还要满足非负且能够被下标减一整除。
这样枚举第一个数,做差判断下即可,时间复杂度 \(O(Tnm)\),有 60pts。
先跑路去看 T2,我草怎么是图计数,这我不会啊。
观察到有树的部分分,我草怎么我树上也不会啊。
好吧只会了前 40pts。
想到了把图拆成 MST,从小到大枚举放水的上界,把小于上界的边连接的点缩起来然后在 MST 上 DP,但是怎么去重啊我草。
40pts 跑路了。
T3 是神秘 DP 优化,首先 20pts \(O(n^4)\) 好说。
然后想到了一个比较好的贪心优化转移,就是考虑两个数组里的每个颜色,在两个数组中尽可能的找最后面出现的位置转移一定是更优的。
这样就 \(O(n^2\times k)\) 了。\(k\) 是颜色种类。70pts。
于是写写写,我草你的怎么要求跳必须两个腿都移动啊,那这贪心假了啊。
哦没有完全假,但是要求必须考虑每个位置上的颜色,退化成 \(O(n^3)\) 了,还有 50pts。
于是继续写,我草怎么大样例炸了。
哦只跑了一个数组,改了改过了。
T4 对于每种操作可以 \(O(n)\) 的建图然后拓扑排序找到每种颜色最后变成了什么,于是有 20pts。
然后就不会了/kel
我草哥们一遍过样例。
我草大样例怎么没给 2000 的,这下又要挂分了哥们。
不是为什么挂分依靠大样例啊我个唐。
回去看 T1,发现判断能否被整除这东西很唐,换成算贡献就有 \(O(Tm\log m)\) 了。
于是写写写,我草哥们又一遍过样例。
我草怎么大样例炸了,这你让我上哪调去。
我草单独测就是对的,那就好了改下清空就对了。
我草怎么最大的点跑了 10s。我卡常卡常卡常。
好的这下开了 o2 就 1.7s 了。
接下来跑路去研究了调色科技。
会挂多少分?
upd:80pts!
挂在 T1 没判上界上了。T4 好像还是被卡常了/fn
T2 降智了竟然没想出来,T3 都说是很套路的斜率优化 DP,T4 是个很有意思的题。
T2
考虑一个隔板两边水位的情况,两边水位可以同时不高于隔板,也可以同时高于隔板。
基于此,我们考虑一个最小生成树的过程,从小到达加入边并合并连通块。
在此之前,我们记录每一个连通块内部水位都不高于自己连通块里最高隔板的情况下的方案数。
这样,合并两个连通块时,两边都不高于连通块里最高隔板的情况是格子独立的,乘起来即可。
还有一种情况就是可能某一边的水位高于了一边连通块的最高隔板但是不高于新连接的这块隔板,这时候情况就只有连通块里的点水位都相同,于是也可以做差直接乘起来。
而且这样你会发现如果出现了连通块里面更高水位的情况一定会在之后合并的时候被考虑到,于是这个算法就可以做到补充不漏了。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=5e5+5;
const int mod=998244353;
struct edge{
int u;int v;int w;
}e[N];
bool cmp(edge x,edge y){
return x.w<y.w;
}
int fa[N],maxw[N];
int _find(int u){
return fa[u]==u?u:fa[u]=_find(fa[u]);
}
ll dp[N];
int main(){
int n,m,h;cin>>n>>m>>h;
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].w;
}
sort(e+1,e+1+m,cmp);
for(int i=1;i<=n;i++){
fa[i]=i;dp[i]=1;
}
int nid=0;
for(int i=1;i<=m;i++){
int t1=_find(e[i].u);
int t2=_find(e[i].v);
if(t1==t2)continue;
nid=i+n;
fa[nid]=nid;
fa[t1]=nid;fa[t2]=nid;
maxw[nid]=e[i].w;
dp[nid]=(dp[t1]+maxw[nid]-maxw[t1]+mod)%mod*(dp[t2]+maxw[nid]-maxw[t2]+mod)%mod;
}
cout<<(dp[nid]+h-maxw[nid]+mod)%mod;
return 0;
}
T3
场上写了个贪心,竟然是对的,但是这个贪心就没有优化的前途了。
有一个小技巧就是我们把强制移动两只脚的过程分成先移动左脚再移动右脚的过程,于是设 DP 式子 \(g_{0/1,i,j}\) 表示当前要移动左/右脚,当前的脚分别在编号为 \(i,j\) 的石头上的最小花费。
这里要注意我们能从一个状态转移必须要满足 \(i,j\) 对应石头的颜色相同,这是题目要求的。
接下来写出转移,单独考虑每一维就是
这是个斜率优化 DP 的经典形式,然后就可以做了。
但是我还不会斜率优化呢(逃
T4
首先想想 \(O(nq)\) 的暴力怎么打。
由于序列一开始是一个位置一个颜色的,我们考虑建图来做,这样就可以通过图推断出各颜色最后变成了什么。
具体来说,我们每次改变颜色时都对应建立一个新点并将要变的颜色和先前的颜色都往这个点连一条有向边。之后将这个点作为这个颜色的代表点存入数组中。
统计答案时,在反图上进行拓扑排序即可得到每个点的颜色,于是有了时间复杂度为 \(O(nq)\) 的暴力。
接下来是正解,这玩意看着就不好在线做,我们把询问离线下来。
首先我们反着来安排操作,考虑固定一个 \(r\),之后向左移动 \(l\),会对整个操作序列产生什么影响。
反着安排操作的意义是:一旦两个位置被合并成同一种颜色,那么之后无论怎样变化颜色,这两个位置的颜色一定是相同的。
又由计算最长连续段的经验,我们只需关心每个位置两边的颜色是否与自己相同即可。于是我们可以借此计算合并两个颜色带来的贡献。
由右向左移动区间左端点的过程也可以用我们刚才的建图方法刻画,考虑加入操作 \(a \rightarrow b\) 后怎么修改图,我们新建一个节点 \(p\),令其颜色为 \(b\) 的颜色,之后将 \(p\) 连接原 \(b\) 的父亲,\(a,b\) 均连接 \(p\),这就完成了一次加入。
现在我们再在 \(p\) 位置记录一个值为其代表的操作的编号。
接下来考虑两个位置 \(i,i+1\),有一个性质是他们在图上的 LCA 就是他们第一次变的相同的操作编号,之后的所有操作这两个位置的颜色都是相同的,于是它就会对连续极长子段的贡献减 \(1\)。这里有一个后缀的性质,我们在线段树上做一次 \([id,n]\) 的后缀加表示这个减一的贡献。
而且,我们每次移动 \(l\) 都只会最多影响 \(3\) 个点,这样我们就可以快速处理求解 LCA 的 fa 数组了。
于是,由于后缀的性质,我们直接钦定 \(r=n\),让 \(l\) 从大向小扫,扫到一个询问的左端点统计答案即可。
综上我们需要加叶子求 LCA 和树状数组,前者可以用倍增动态维护。时间复杂度为 \(O(n\log n)\)。
代码是比较好写的,感谢 bzq 神仙提供的如此简单的实现样本!
code
Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e6+6;
int ml[N],mr[N];
const int lgr=19;
int fa[N][30];
int dep[N];
int nw[N];
int n,m,q;
int lca(int u,int v){
if(!dep[u]||!dep[v])return m+1;
if(u==v)return nw[u];
if(dep[u]<dep[v])swap(u,v);
for(int i=lgr;i>=0;i--)
if(dep[fa[u][i]]>=dep[v])u=fa[u][i];
for(int i=lgr;i>=0;i--)
if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return nw[fa[u][0]];
}
int c[N];
void add(int p,int k){
for(;p<=m;p+=(p)&(-p))c[p]+=k;
return ;
}
int qry(int p){
int sub=0;
for(;p>=1;p-=(p)&(-p))sub+=c[p];
return sub;
}
int nid=0;
vector<pair<int,int> > ql[N];
int ansl[N];
int main(){
n=rd;m=rd;q=rd;
for(int i=1;i<=m;i++)ml[i]=rd,mr[i]=rd;
for(int i=1;i<=q;i++){
int l;int r;
l=rd;r=rd;
ql[l].push_back(mkp(r,i));
}
nid=n;
nw[0]=m+1;
for(int i=m;i>=1;i--){
int u=ml[i],v=mr[i];
++nid;
int l1=lca(u,u-1);int l2=lca(u,u+1);
add(l1,-1);add(l2,-1);
dep[nid]=dep[fa[v][0]]+1;
dep[u]=dep[nid]+1;dep[v]=dep[nid]+1;
fa[nid][0]=fa[v][0];
fa[u][0]=nid;fa[v][0]=nid;
nw[nid]=i;
for(int j=1;j<=lgr;j++){
fa[u][j]=fa[fa[u][j-1]][j-1];
fa[nid][j]=fa[fa[nid][j-1]][j-1];
fa[v][j]=fa[fa[v][j-1]][j-1];
}
l1=lca(u,u-1);l2=lca(u,u+1);
add(l1,1);add(l2,1);
for(pair<int,int> v:ql[i]){
int r=v.first;int id=v.second;
ansl[id]=n-qry(r);
}
}
for(int i=1;i<=q;i++){
cout<<ansl[i]<<'\n';
}
return 0;
}

浙公网安备 33010602011771号