5月26日
5月26日
Love is the best medicine.
前传
“同学们,这种试卷我们明天订正完‘反交’一下。”——5.25 Mr 袁
这就是生物的魅力吗?
“赫鲁晓夫提出了两个条件:一个是要到美国的肯尼迪(???)玩?”——Miss 殷
“嗯???” 全班
“哦不对,迪士尼” 笑
第二次“没有答应他去肯尼迪”????
这就是历史的魅力吗
CF1527E Partition Game
线段树优化dp
对于暴力 \(dp[i][j]\) 表示前i个分为j段的值
转移 \(dp[i][k] = min \{dp[j][k-1]+cost(j+1,i)\}\)
对于cost,从后往前枚举 \(j\) 计算时只要加入 \(j+1\) 位置和后面第一个的差就可以 \(O(n^2k)\) 完成了。
如果从前忘后算,加入 \(i\) 的时候,也就是将 \([1,lst]\) 的代价都加 \(i-lst\) 就可以用线段树区间加求最小值啦!枚举一下k,就可以 \(O(kn log n)\) 啦!
#include <bits/stdc++.h>
typedef long long ll;
const int N=35005;
const ll INF=1e15;
ll dp[N];
int a[N],pre[N],n,k,lst[N];
struct SGT{
ll t[N<<2],tg[N<<2];
void build(int k,int L,int R){
tg[k]=0;
if (L==R){
t[k]=dp[L-1];
return;
}
int mid=(L+R)>>1;
build(k<<1,L,mid),build(k<<1|1,mid+1,R);
t[k]=std::min(t[k<<1],t[k<<1|1]);
}
void puttag(int k,int x){
tg[k]+=x;
t[k]+=x;
}
void pushdown(int k){
if (tg[k]){
puttag(k<<1,tg[k]);
puttag(k<<1|1,tg[k]);
tg[k]=0;
}
}
void modify(int k,int L,int R,int l,int r,int x){
if (r<l) return;
if (L==l && R==r){
puttag(k,x);
return;
}
pushdown(k);
int mid=(L+R)>>1;
if (r<=mid) modify(k<<1,L,mid,l,r,x);
else if (l>mid) modify(k<<1|1,mid+1,R,l,r,x);
else{
modify(k<<1,L,mid,l,mid,x);
modify(k<<1|1,mid+1,R,mid+1,r,x);
}
t[k]=std::min(t[k<<1],t[k<<1|1]);
}
ll query(int k,int L,int R,int l,int r){
if (l==L && R==r) return t[k];
pushdown(k);
int mid=(L+R)>>1;
if (r<=mid) return query(k<<1,L,mid,l,r);
if (l>mid) return query(k<<1|1,mid+1,R,l,r);
return std::min(query(k<<1,L,mid,l,mid),query(k<<1|1,mid+1,R,mid+1,r));
}
}T;
int main(){
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++){
pre[i]=lst[a[i]];
lst[a[i]]=i;
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for (int i=1;i<=k;i++){
T.build(1,1,n);
dp[i-1]=INF;
for (int j=i;j<=n;j++){
T.modify(1,1,n,1,pre[j],j-pre[j]);
dp[j]=T.query(1,1,n,1,j);
}
}
printf("%lld\n",dp[n]);
}
CF1515F Phoenix and Earthquake
题目名指路 歌曲名:涅槃 (Phoenix) 敲代码超配
构造
瞄了一眼发现什么boruvka,然后想到最后得出的猜测是。
我也不会boruvka怎么做(后来发现人家说他假了,我眼瞎),感觉要是和 \(<(n-1) \times k\) 一定无解。否则每次把可行的并掉一定就可以了吧。
至于怎么并可行的,找任意一棵生成树都是可行的,因为如果总和满足条件,一定有一条边是 \(>2k\) 的,否则达不到 \((n-1) \times k\) 。
然后的构造:
每次找到一个叶节点 \(u\)
- 如果 \(a_u<k\) 那么把 \(u\) 在最后和父亲合起来,继续考虑剩下部分(剩下部分的和一定满足),压入栈
- 否则就可以当前就把 \(u\) 和 \(fa\) 连起来,变成 \(a_u+a_{fa}-k\),继续构造,直接输出
最后把第一类边倒序输出即可
#include <bits/stdc++.h>
typedef long long ll;
const int N=300005;
std::queue<int> q;
int n,m,k,f[N],to[N<<1],edge,Next[N<<1],last[N],id[N<<1],fw[N],st[N],tp,x,y;
int d[N];
ll a[N],sum;
int find(int x){
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
void add(int x,int y,int z){
to[++edge]=y;
Next[edge]=last[x];
last[x]=edge;
id[edge]=z;
}
void dfs(int x,int y,int z){
f[x]=y,fw[x]=z;
for (int i=last[x];i;i=Next[i]){
int u=to[i];
if (u==y) continue;
dfs(u,x,id[i]);
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;i++) f[i]=i;
for (int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum+=a[i];
}
if (sum<(ll)(n-1)*k) return puts("NO"),0;
puts("YES");
for (int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
int fx=find(x),fy=find(y);
if (fx==fy) continue;
add(x,y,i),add(y,x,i);
d[x]++,d[y]++;
f[fx]=fy;
}
dfs(1,0,0);
for (int i=2;i<=n;i++)
if (d[i]==1) q.push(i);
while (q.size()){
int x=q.front(),fa=f[x];
q.pop();
if (!fa) continue;
d[fa]--;
if (d[fa]==1) q.push(fa);
if (a[x]<k) st[++tp]=fw[x];
else printf("%d\n",fw[x]),a[fa]=a[fa]+a[x]-k;
}
while (tp) printf("%d\n",st[tp--]);
}
CF1477D Nezzar and Hidden Permutations
拓扑序,构造
这题我以前一定看过,然而我想了半天还是不会做。
开始一下午一道题的摸鱼时光。
首先如果一个点度数是 \(n-1\) ,定向后这个点的拓扑序一定唯一确定了,可以把这个点去掉。
去完以后,最大点的度数 \(<n-1\),猜测此时已经有方案使两排列完全不相同。
考虑如果一张不止一个点的图中有一个孤立点,那么这个孤立点拓扑序可以任意,只要放在第一个+其他、其他+最后一个就可以了,称次为孤立图。
那么我们努力把剩下的图分成若干个有孤立点的集合,这样把这些图的拓扑序拼起来(一段一段拼就行)后也不会相同(只要按集合编号大小连定外面的边就行了)。这些孤立图在反图中的表现即为菊花图。建出反图的一棵生成树,从下往上处理 。如果子节点还未被加入某一个集合,那么和父亲并在一起,父亲为孤立点。到最后如果根成为了单独的点,任选一个它的儿子加入进去即可。
反图的边数数量很多,如何建一棵生成树?随便了。看到了一种简单的写法,虽然他是 \(O(n log n)\) 的,就这样吧。
#include <bits/stdc++.h>
using namespace std;
const int N=500005;
int T,n,m,x,y,d[N],match[N],a[N],b[N],siz[N],ap[N],bp[N];
vector<int> e[N],s[N];
queue<int> q;
void clear(){
for (int i=1;i<=n;i++)
d[i]=0,e[i].clear(),match[i]=0,s[i].clear();
}
int main(){
scanf("%d",&T);
while (T--){
clear();
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
d[x]++,d[y]++;
e[x].push_back(y),e[y].push_back(x);
}
for (int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
int tp=0;
for (int i=1;i<=n;i++){
if (d[i]==n-1){
a[++tp]=i;
b[tp]=i;
continue;
}
if (match[i]) continue;
int p=0,u;
for (u=1;u<=n;u++){
if (u==i) continue;
if (p>=e[i].size() || u!=e[i][p]){
if (d[i]!=n-1) break;
}else p++;
}
int rt=match[u];
if (!rt){//新建
rt=u;
match[i]=match[u]=u;
siz[u]=2;
}else{
if (rt==u){//直接加入
siz[rt]++;
match[i]=rt;
}else{
if (siz[rt]>2){//分裂
siz[rt]--;
rt=u;
match[u]=match[i]=u;
siz[u]=2;
}else{//换根
siz[u]=3;
match[rt]=match[u]=match[i]=u;
}
}
}
}
for (int i=1;i<=n;i++)
if (match[i] && match[i]!=i) s[match[i]].push_back(i);
for (int i=1;i<=n;i++){
if (match[i]==i){
a[tp+1]=i;
b[tp+siz[i]]=i;
for (int j=0;j<siz[i]-1;j++){
a[tp+1+j+1]=s[i][j];
b[tp+1+j]=s[i][j];
}
tp+=siz[i];
}
}
for (int i=1;i<=tp;i++) ap[a[i]]=i,bp[b[i]]=i;
for (int i=1;i<=n;i++) printf("%d ",ap[i]);puts("");
for (int i=1;i<=n;i++) printf("%d ",bp[i]);puts("");
}
}
CF1515G Phoenix and Odometers
昨天回家想了想。你保存了吗?保存了吗???
今天打开 哦 果然是一片空白,真是太棒了,清理一下心情。
至于为什么弄了这么久,Longlong不要直接用abs,死的不明不白。
还学会了gcd的新写法,这样就不用管0了。
果断ctrl+s
哦它恢复回来了
想了一会儿.
对不起没有然后了我以为它是个无向图,还想着每条边都可以绕来绕去绕成余数为零。
重新开始!有向图,回路,那一定是一个强连通分量里的,不妨只考虑某一个。且两两间都有回路,那么易知,整个强连通分量对 \((s,t)\) 的答案是一样的,因为你可以从 \(v\) 绕一个回路绕t次就余数为0了,中途再从 \(u\) 绕出去一下,这样 \(u\) 能达到的 \(v\) 也行了。
同理也可以知道,所有的环都可以任意取到,如果把图中所有环长取出来,问的就是 \(k_1 len_1+k_2 len_2 + \dots k_t len_t +S\equiv 0 (\text{mod } T)\)
根据裴蜀定理可知,前面那一堆东西能表示出任意\(kg(g=gcd(len))\) 那么成立条件即为 \(gcd(g,T)|S\)
问题转化为怎么求所有环长
因为是有向图,所以任意一个环都可以表示成树上返祖边环的组合。
#include <bits/stdc++.h>
typedef long long ll;
const int N=200005;
int dfn[N],low[N],idn,col[N],to[N],last[N],Next[N],w[N],edge;
int x,y,n,Q,vis[N],st[N],tp,cnt,z,m,s,t;
ll d[N],a[N];
ll g;
ll gcd(ll x,ll y){
return y?gcd(y,x%y):x;
}
void add(int x,int y,int z){
to[++edge]=y;
Next[edge]=last[x];
last[x]=edge;
w[edge]=z;
}
void tarjan(int x){
dfn[x]=low[x]=++idn;
vis[x]=1;
st[++tp]=x;
for (int i=last[x];i;i=Next[i]){
int u=to[i];
if (!dfn[u]){
tarjan(u);
low[x]=std::min(low[x],low[u]);
}else if (vis[u]) low[x]=std::min(low[x],dfn[u]);
}
if (dfn[x]==low[x]){
cnt++;
int u;
do{
u=st[tp--];
vis[u]=0;
col[u]=cnt;
}while (u!=x);
}
}
ll Abs(ll x){
return x>0?x:-x;
}
void dfs(int x,int c){
vis[x]=1;
for (int i=last[x];i;i=Next[i]){
int u=to[i];
if (col[u]!=c) continue;
if (!vis[u]){
d[u]=d[x]+w[i];
dfs(u,c);
}else {
ll t=Abs(d[x]-d[u]+w[i]);
g=gcd(g,t);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i);
for (int i=1;i<=n;i++){
if (vis[i]) continue;
g=0;
dfs(i,col[i]);
a[col[i]]=g;
}
scanf("%d",&Q);
while (Q--){
scanf("%d%d%d",&x,&s,&t);
x=col[x];
if ((!s) || s%gcd(a[x],t)==0) puts("YES");
else puts("NO");
}
}