【做题记录】csp2025-贪心专题

A. [NOIP2015 普及组] 推销员
首先考虑一个明显假的贪心,选择前 \(X\) 大的疲劳值计算答案。
它假就假在,可以选择一个(或几个)疲劳值更小,但更远的位置,使总贡献更大。
略经思考后发现,如果要更换,那么一定要满足距离比当前的所有都远,而且更换掉的一定是当前最小的疲劳值。
同时,如果更换 \(2\) 个,则新的这 \(2\) 个疲劳值一定会都比当前的小,而距离却只会按更远的那个计算,因此一定不如更换一个更优。
故只需从前 \(X\) 大和更换掉一个的两种答案中取 \(\max\) 即可。具体实现可以用前缀和、前后缀最小值。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=1e5+5; int n,sum[maxn],f[maxn],g[maxn]; struct node{ int a,b; }p[maxn]; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n); for(int i=1;i<=n;i++){ read(p[i].a); } for(int i=1;i<=n;i++){ read(p[i].b); } sort(p+1,p+n+1,[](const node &x,const node &y){return x.b>y.b;}); for(int i=1;i<=n;i++){ sum[i]=sum[i-1]+p[i].b; f[i]=max(f[i-1],p[i].a<<1); } for(int i=n;i;i--){ g[i]=max(g[i+1],(p[i].a<<1)+p[i].b); } for(int i=1;i<=n;i++){ printf("%d\n",max(sum[i]+f[i],sum[i-1]+g[i])); } return 0; } } int main(){return asbt::main();}

B. Two Heaps
考虑如果所有数都不一样,那么答案就为 \(n^2\)
如果有一个数出现了两次,那么显然一个放这边另一个放那边更优。
如果出现次数大于 \(2\),那么剩下的是做不了贡献的,乱放。
因此步骤为:
\(1.\) 将所有出现次数 \(\ge 2\) 的在两边各放一个,剩下的留着。
\(2.\) 将所有出现次数为 \(1\) 的平均分配给两边。
\(3.\) 用第 \(1\) 步中剩下的数填满两个集合。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=205; int n,ans[maxn],num[maxn]; struct node{ int zhi,hao; }a[maxn]; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n); for(int i=1;i<=n<<1;i++){ read(a[i].zhi); num[a[i].zhi]++; a[i].hao=i; } sort(a+1,a+(n<<1|1),[](const node &x,const node &y){return x.zhi<y.zhi;}); int cnt1=0,cnt2=0; for(int i=10;i<=99;i++){ if(num[i]>1){ cnt1++,cnt2++; } } for(int i=10;i<=99;i++){ if(num[i]==1){ if(cnt1<=cnt2){ cnt1++; } else{ cnt2++; } } } printf("%d\n",cnt1*cnt2); cnt1=cnt2=0; for(int i=1;i<=n<<1;i++){ if(num[a[i].zhi]>1){ ans[a[i].hao]=1; ans[a[++i].hao]=2; cnt1++,cnt2++; while(a[i+1].zhi==a[i].zhi){ i++; } } } for(int i=1;i<=n<<1;i++){ if(num[a[i].zhi]==1){ if(cnt1<=cnt2){ cnt1++; ans[a[i].hao]=1; } else{ cnt2++; ans[a[i].hao]=2; } } } for(int i=1;i<=n<<1;i++){ if(num[a[i].zhi]>2){ i++; while(a[i+1].zhi==a[i].zhi){ i++; if(cnt1<=cnt2){ cnt1++; ans[a[i].hao]=1; } else{ cnt2++; ans[a[i].hao]=2; } } } } // cout<<cnt1<<" "<<cnt2<<"\n"; for(int i=1;i<=n<<1;i++){ printf("%d ",ans[i]); } return 0; } } int main(){return asbt::main();}

C. Antichain
首先,题目给出的图一定是一堆链构成的,其中有的点只在一条链中,有的同时在两条链中。

首先,一条链中一定只能选一个点,即答案最大为链的数量。然后考虑这么个事,如果选择了只在一条链中的点,那么会废掉一条链;然而如果选择了在两条链中的点,那就会废掉两条链。因此贪心策略为首先选择只在一条链中的点,然后再考虑在两条链中的点。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=1e6+5; int n; char s[maxn]; bitset<maxn> vis; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ scanf(" %s",s); n=strlen(s); int ans=0; for(int u=0,v;u<n;u++){ if(vis[u]){ continue; } if(s[u]==s[(u-1+n)%n]){ // cout<<u<<"\n"; ans++,vis[u]=1,v=u; while(s[v]==s[u]){ v=(v+1)%n; vis[v]=1; } v=u; while(s[(v-1+n)%n]==s[u]){ v=(v-1+n)%n; vis[v]=1; } } } for(int u=0;u<n;u++){ if(vis[u]){ continue; } // cout<<u<<"\n"; ans++; vis[u]=vis[(u+1)%n]=vis[(u-1+n)%n]=1; } printf("%d",ans); return 0; } } int main(){return asbt::main();}

D. [AGC057A] Antichain of Integer Strings
\(f(x)\) 为最小的大于 \(x\)\(y\),使得 \(x\)\(y\) 的子串。易得:

\[f(x)=\min(10x,x+10^{|x|}) \]

其中 \(|x|\) 表示 \(x\) 的位数。
可以发现,\(f(x)\) 为一个严格单调递增的函数。
考虑贪心策略,显然选小的数不如选大的数优,因为小的数更有可能成为别的数的子串。于是,我们要求的其实就是这样一个集合 \(\mathbb{A}\),满足:

\[\mathbb{A}=\{x\in[l,r]\mid f(x)>r\} \]

因为 \(f(x)\) 是严格单增的,因此二分即可。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define int ll using namespace std; namespace asbt{ namespace cplx{bool begin;} const int pw10[]={ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000}; il int len(int x){ int res=0; do{ res++,x/=10; }while(x); return res; } il int f(int x){ return min(x*10,x+pw10[len(x)]); } namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } signed main(){ // for(int i=1;i<=33;i++){ // cout<<i<<" "<<f(i)<<"\n"; // } int T; read(T); while(T--){ int l,r,L,R; read(l)read(r); L=l,R=r; while(l<r){ int mid=(l+r)>>1; if(f(mid)>R){ r=mid; } else{ l=mid+1; } } printf("%d\n",R-l+1); } return 0; } } signed main(){return asbt::main();}

E. [USACO10MAR] Barn Allocation G
先按左端点升序排序,再按右端点升序排序。然后跑一个线段树区间减一,区间取 \(\min\)。这样的做法拿了 \(57 pts\)。然后再反着跑一遍,就过了。
原因是,正解做法为按右端点升序排序然后正着跑,我是按左端点升序排序再反着跑,那肯定是一样的。。。
正确性证明,因为按右端点升序排序,所以新加的右端点大于后加的。如果有重合,选新的而不选旧的会导致白白给多出来的这一块减了一个 \(1\),显然不如不选优。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define lid id<<1 #define rid id<<1|1 using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=1e5+5; int n,m,a[maxn]; struct node{ int l,r; }p[maxn]; struct stree{ int zhi[maxn<<2],tag[maxn<<2]; il void pushup(int id){ zhi[id]=min(zhi[lid],zhi[rid]); } il void pushdown(int id){ if(tag[id]){ tag[lid]+=tag[id],tag[rid]+=tag[id]; zhi[lid]+=tag[id],zhi[rid]+=tag[id]; tag[id]=0; } } il void build(int id,int l,int r){ tag[id]=0; if(l==r){ zhi[id]=a[l]; return ; } int mid=(l+r)>>1; build(lid,l,mid); build(rid,mid+1,r); pushup(id); } il void upd(int id,int L,int R,int l,int r,int val){ if(L>=l&&R<=r){ tag[id]+=val,zhi[id]+=val; return ; } pushdown(id); int mid=(L+R)>>1; if(l<=mid){ upd(lid,L,mid,l,r,val); } if(r>mid){ upd(rid,mid+1,R,l,r,val); } pushup(id); } il int query(int id,int L,int R,int l,int r){ if(L>=l&&R<=r){ return zhi[id]; } pushdown(id); int mid=(L+R)>>1; if(r<=mid){ return query(lid,L,mid,l,r); } if(l>mid){ return query(rid,mid+1,R,l,r); } return min(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r)); } }SG; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n)read(m); for(int i=1;i<=n;i++){ read(a[i]); } for(int i=1;i<=m;i++){ read(p[i].l)read(p[i].r); } sort(p+1,p+m+1,[](const node &x,const node &y){return x.l<y.l||x.l==y.l&&x.r<y.r;}); SG.build(1,1,n); int ans1=0,ans2=0; for(int i=1;i<=m;i++){ if(SG.query(1,1,n,p[i].l,p[i].r)){ SG.upd(1,1,n,p[i].l,p[i].r,-1); ans1++; // cout<<i<<"\n"; } } SG.build(1,1,n); for(int i=m;i;i--){ if(SG.query(1,1,n,p[i].l,p[i].r)){ SG.upd(1,1,n,p[i].l,p[i].r,-1); ans2++; // cout<<i<<"\n"; } } printf("%d",max(ans1,ans2)); return 0; } } int main(){return asbt::main();}

F. [USACO09OCT] Allowance G
因为所有面值都可以整除比它大的面值,所以大的面值一定可以用小的凑出来,所以优先用大面值,剩下的用一个小面值来补就行了。
时间复杂度,每个硬币最多被用一次,这一部分是 \(O(\sum B)\);死循环中每次的硬币选择方式都是不同的,这一部分是 \(O(n2^n)\),都可以过。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pii pair<int,int> #define mp make_pair #define fir first #define sec second #define pb push_back using namespace std; namespace asbt{ namespace cplx{bool begin;} int n,m,ans; vector<pii> q; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n)read(m); for(int i=1,a,b;i<=n;i++){ read(a)read(b); if(a>=m){ ans+=b; } else{ q.pb(mp(a,b)); } } sort(q.begin(),q.end()); for(;;){ int tmp=m; for(int i=q.size()-1;~i;i--){ while(tmp>=q[i].fir&&q[i].sec){ tmp-=q[i].fir,q[i].sec--; } } if(tmp>0){ for(int i=0;i<q.size();i++){ if(q[i].fir>=tmp&&q[i].sec){ q[i].sec--,tmp=0; break; } } } if(tmp>0){ break; } ans++; } printf("%d",ans); return 0; } } int main(){return asbt::main();}

G. Competition
如果在一个 \(n\) 阶楼梯上有 \(\le n\) 位运动员,则一定是合法的。
考虑每个运动员对应着一个区间 \([l,r]\),表示这个人能到达的台阶。举个例子:

把台阶的顶端从左往右编号,则 \(1\) 对应 \([1,3]\)\(2\) 对应 \([2,3]\)\(3\) 对应 \([4,4]\)\(4\) 对应 \([3,5]\)
那么我们只需要选择一些区间,满足存在一些点使可以不重复地给每个区间一个包含的点。
这是一个贪心,将所有区间按左端点排序,从左向右扫 \(n\) 个位置,扫到 \(i\) 时加入左端点为 \(i\) 的区间,此时所有的区间都是包含了 \(i\) 的,贪心地选择右端点最小的,然后再删去右端点为 \(i\) 的区间。具体实现可以用堆。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=1e5+5; int n,m; struct node{ int id,l,r; il bool operator<(const node &x)const{ return r>x.r; } }p[maxn]; priority_queue<node> q; vector<int> ans; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n)read(m); for(int i=1,x,y;i<=m;i++){ read(x)read(y); p[i]=(node){i,n-y+1,x}; } sort(p+1,p+m+1,[](const node &x,const node &y){return x.l<y.l;}); for(int i=1,j=1;i<=n;i++){ while(p[j].l==i){ q.push(p[j++]); } if(q.size()){ ans.pb(q.top().id); q.pop(); } while(q.size()&&q.top().r==i){ q.pop(); } } printf("%d\n",ans.size()); for(int x:ans){ printf("%d ",x); } return 0; } } int main(){return asbt::main();}

H. Jeff and Permutation
首先将所有 \(a_i\) 都取绝对值,不影响答案。
考虑怎样会产生逆序对(\(i<j\)):

  • \(a_i<a_j\),则需要把 \(a_j\) 变成负的,\(a_i\) 变不变无所谓。
  • \(a_i>a_j\),则 \(a_i\) 不能变,\(a_j\) 变不变无所谓。

因此统计 \(a_i\) 前面比它小的、后面比它小的,即将 \(a_i\) 改为负数、不改为负数会增加的逆序对数,二者取 \(\min\) 即可。
在左侧不能取小于等于,因为如果 \(a_i\) 的左侧比它小的数都已经小于右侧了,那么它左边的一个相同的数肯定也是这样。换句话说对于同一个数,一定是前面一部分变成负的,后面一部分不变。所以相同的数之间是不会产生贡献的。
\(a_i\) 变不变号也不会影响其他位置的答案,因为较小的数变或不变号都是不会影响答案的。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=2e3+5; int n,a[maxn]; namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n); for(int i=1;i<=n;i++){ read(a[i]); a[i]=abs(a[i]); } int ans=0; for(int i=1,cnt1,cnt2;i<=n;i++){ cnt1=cnt2=0; for(int j=1;j<i;j++){ if(a[j]<a[i]){ cnt1++; } } for(int j=i+1;j<=n;j++){ if(a[j]<a[i]){ cnt2++; } } ans+=min(cnt1,cnt2); } printf("%d",ans); return 0; } } int main(){return asbt::main();}

I. [HEOI2015] 兔子与樱花
贪心策略:从下往上,对于每个点不断选择代价最小的儿子删除。
正确性:首先,选择最小的代价来删显然是没问题的。考虑在节点 \(u\),若删除了它的一个儿子,那么可能会导致它的父亲本来可以删它,现在却删不了了。这样先多删一个再少删一个,是不会影响答案的。因此此策略正确。

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=2e6+5; int n,m,a[maxn],ans; vector<int> e[maxn]; il void dfs(int u){ for(int v:e[u]){ dfs(v); } sort(e[u].begin(),e[u].end(),[](const int &x,const int &y){return a[x]<a[y];}); for(int v:e[u]){ if(a[u]+a[v]-1<=m){ a[u]+=a[v]-1; ans++; } else{ break; } } } namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n)read(m); for(int i=0;i<n;i++){ read(a[i]); } for(int i=0,tot;i<n;i++){ read(tot); a[i]+=tot; for(int j=1,x;j<=tot;j++){ read(x); e[i].pb(x); } } dfs(0); printf("%d",ans); return 0; } } int main(){return asbt::main();}

J. 展翅翱翔之时 (はばたきのとき)
\(a_i\)\(i\) 连边,题目给出的就是一个外向基环树森林。题目的要求是将它变成一个环。考虑将每棵树分开考虑。
思路是先断成一条条链,然后再连起来。对于每个子树上的点,只保留代价最大的儿子,其他儿子全部删去。这样就变成了一个环,向外伸出一堆链的形态。
现在枚举环上的每个点,设为 \(u\),则此时 \(a_u\) 是连出了两条边的(连向 \(u\) 的和伸出链的),一定要删去一条。记录删去所有链的代价和将环断开并保留一条链的代价,进行转移即可。时间复杂度 \(O(n)\)

Code
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
#include<bits/stdc++.h> #define ll long long #define il inline #define read(x){\ char ch;\ int fu=1;\ while(!isdigit(ch=getchar()))\ fu-=(ch=='-')<<1;\ x=ch&15;\ while(isdigit(ch=getchar()))\ x=(x<<1)+(x<<3)+(ch&15);\ x*=fu;\ } #define pb push_back using namespace std; namespace asbt{ namespace cplx{bool begin;} const int maxn=1e5+5; const ll inf=0x3f3f3f3f3f3f3f3f; int n,a[maxn],deg[maxn]; int cnt,idx[maxn]; ll b[maxn],ans,liu[maxn]; bitset<maxn> vis; vector<int> e[maxn]; il void dfs1(int u){ vis[u]=1; idx[++cnt]=u; for(int v:e[u]){ if(vis[v]){ continue; } dfs1(v); } } queue<int> q; il void dfs2(int u){ ll mx=0,sum=0; for(int v:e[u]){ if(deg[v]){ continue; } sum+=b[v],mx=max(mx,b[v]); dfs2(v); } ans+=sum-mx,liu[u]=mx; } namespace cplx{ bool end; il double usdmem(){return (&begin-&end)/1048576.0;} } int main(){ read(n); for(int i=1;i<=n;i++){ read(a[i])read(b[i]); e[a[i]].pb(i); deg[a[i]]++; } for(int i=1;i<=n;i++){ if(!deg[i]){ q.push(i); } } while(q.size()){ int u=q.front(); q.pop(); if(--deg[a[u]]==0){ q.push(a[u]); } } for(int x=1,tot;x<=n;x++){ if(vis[x]||!deg[x]){ continue; } // cout<<x<<"\n"; tot=cnt+1; // puts("666"); dfs1(x); if(cnt-tot+1==n){ for(int i=1;i<=n;i++){ if(!deg[i]){ goto togo; } } puts("0"); return 0; togo:; } for(int i=tot;i<=cnt;i++){ if(deg[idx[i]]){ dfs2(idx[i]); } } // cout<<ans<<"\n"; ll sum=0,res=inf; for(int i=tot;i<=cnt;i++){ if(deg[idx[i]]){ res=min(min(sum,res)+b[idx[i]],res+liu[a[idx[i]]]); sum+=liu[a[idx[i]]]; } } ans+=res; } printf("%lld",ans); return 0; } } int main(){return asbt::main();} /* 10 3 12 5 6 1 16 5 3 7 3 10 11 4 20 10 12 7 7 10 3 32 */
posted @   zhangxy__hp  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开