【做题记录】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
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
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
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\) 的子串。易得:
其中 \(|x|\) 表示 \(x\) 的位数。
可以发现,\(f(x)\) 为一个严格单调递增的函数。
考虑贪心策略,显然选小的数不如选大的数优,因为小的数更有可能成为别的数的子串。于是,我们要求的其实就是这样一个集合 \(\mathbb{A}\),满足:
因为 \(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
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
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
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
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
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
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
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
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步