筑基赛Day 3总结及题目简单梳理
没什么好总结的,纯纯炸掉,全看T5了,还没写出来,现在想起来就是泪啊、
T1
NOIP春季联赛的时候写过了,可以看看学长的题解,在这里就不多提了。
T2 Barn Tree
我们已知每个节点权值,则可以知道每个节点最终的权值 \(k=tot/n\),其中 \(tot\) 为权值之和。
我们可以考虑去掉一条边,则树被分成两个子图,这两个子图互相独立。
设子图内节点个数是 \(siz\),总共需要 \(siz×k\) 个干草捆,所以其内部干草捆总数 \(hs\) 一定不变。
所以可以判断出一个子图干草捆总数过剩还是不足,还是正好。
然后就可以计算出每条边是否使用,运送的方向,运送的量。
考虑树形DP,维护 \(siz_u\),\(hs_u\)。
然后考虑如何输出方案。显然,拓扑序将会是一个正确的输出顺序。因为这个方法保证了每条必须使用的边都被使用了 1 次,且没有使用其他的边,所以命令数一定是最小的。
代码较为简单。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=2e5+100;
int n,k;
int a[N];
vector<int> G[N];
struct NODE{
int v,w;
};
int siz[N],din[N];
int hs[N];
vector<NODE> ng[N];
void dfs(int u,int fa)
{
siz[u]=1,hs[u]=a[u];
for(auto v:G[u])
{
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v],hs[u]+=hs[v];
}
int d=hs[u]-siz[u]*k;
if(d>0) ng[u].pb({fa,d});
else if(d<0) ng[fa].pb({u,-d});
}
void topsort()
{
int cnt=0;
for(int i=1;i<=n;i++)
{
cnt+=ng[i].size();
for(auto j:ng[i])
din[j.v]++;
}
cout<<cnt<<endl;
queue<int> q;
for(int i=1;i<=n;i++)
if(!din[i])
q.push(i);
while(q.size())
{
int t=q.front();q.pop();
for(auto k:ng[t])
{
cout<<t<<" "<<k.v<<" "<<k.w<<endl;
if(!--din[k.v]) q.push(k.v);
}
}
}
signed main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],k+=a[i];
k/=n;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
G[u].pb(v),G[v].pb(u);
}
dfs(1,0),topsort();
return 0;
}
T3 Taxi
一眼贪心题。
显然,总路程=空载路程+载人(牛)路程。显然载人(牛)路程不变,所以总路程最短即空载路程最短。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+100;
int n,m;
int x[N],y[N];
int ans;
signed main()
{
freopen("taxi.in","r",stdin);
freopen("taxi.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i],ans+=abs(x[i]-y[i]);
x[++n]=m,y[n]=0;
sort(x+1,x+n+1),sort(y+1,y+n+1);
for(int i=1;i<=n;i++)
ans+=abs(x[i]-y[i]);
return cout<<ans<<endl,0;
}
T4 Promotion Counting
题意:求对于每个节点,其子树中权值比自己大的节点个数
正解显然是不会的,看到二位数点(今天晚上看看吧)或者线段树合并的Tag就知道对于我这种蒟蒻来说必须剑走偏锋。
考虑分块。
我们显然可以读入数据,然后对其进行离散化。然后通过DFS求出自己最后的下属。然后分块,在块内根据权值排序,询问时用自己为 \(l\),以自己最后的下属为 \(r\),直接二分该块。代码非常简单。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=1e5+100;
int n,t;
struct NODE{
int num,id,pos;
}dt[N];
vector<int> G[N];
int ys[N];
void lsh()
{
for(int i=1;i<=n;i++) ys[i]=dt[i].num;
sort(ys+1,ys+n+1);
int tmp=unique(ys+1,ys+n+1)-ys-1;
for(int i=1;i<=n;i++)
dt[i].num=lower_bound(ys+1,ys+tmp+1,dt[i].num)-ys;
}
int tail[N],idx;
int dfs(int x)
{
dt[x].id=++idx;
if(G[x].size()==0) return tail[x]=x;
for(auto k:G[x])
{
int j=dfs(k);
if(dt[j].id>dt[tail[x]].id) tail[x]=j;
}
return tail[x];
}
int fz[N];
int L[N],R[N];
int pos[N],num[N];
int ans[N];
int ask(int l,int r,int k)
{
if(l>r) return 0;
int p=pos[l],q=pos[r],res=0;
if(p==q)
{
for(int i=l;i<=r;i++)
if(dt[i].num>=k)
res++;
return res;
}
for(int i=l;i<=R[p];i++)
if(dt[i].num>=k)
res++;
for(int i=p+1;i<q;i++)
res+=R[i]+1-(lower_bound(num+L[i],num+R[i]+1,k)-num);
for(int i=L[q];i<=r;i++)
if(dt[i].num>=k)
res++;
return res;
}
signed main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
cin>>n;t=sqrt(n);
for(int i=1;i<=n;i++)
cin>>dt[i].num,dt[i].pos=i;
for(int i=2;i<=n;i++)
{
int x;
cin>>x;
G[x].pb(i);
}
lsh(),dfs(1);
sort(dt,dt+n+1,[&](NODE a,NODE b){
return a.id<b.id;
});
//for(int i=1;i<=n;i++) cout<<tail[i]<<endl;
for(int i=1;i<=n;i++) fz[dt[i].pos]=dt[i].id;
for(int i=1;i<=t;i++)
L[i]=(i-1)*t+1,R[i]=i*t;
if(R[t]<n)
t++,L[t]=R[t-1]+1,R[t]=n;
for(int i=1;i<=t;i++)
{
for(int j=L[i];j<=R[i];j++)
pos[j]=i,num[j]=dt[j].num;
sort(num+L[i],num+R[i]+1);
}
for(int i=1;i<=n;i++)
ans[dt[i].pos]=ask(i+1,fz[tail[dt[i].pos]],dt[i].num+1);
for(int i=1;i<=n;i++)
cout<<ans[i]<<endl;
return 0;
}
T5 Circular Barn
还是推荐官方题解
博弈论
通过观察(打表)发现 Farmer John获胜当且仅当 \(a\) 不能被 \(4\) 整除,所以我们可以显然知道John一定会剪掉 \(1\) 或 \(2\) 或 \(3\)。而如果\(a\)可以被 \(4\) 整除的时候,显然Farmer Nhoj获胜。
因此就可以确定策略:选择一个与 \(a\) 模 \(4\) 同余的有效值,并相应地减少 \(a\)。
失败者希望最大化游戏的回合数,而获胜者希望最小化游戏的回合数,那么游戏将在多少回合后结束?当a1为偶数时,我们可以通过归纳法证明答案恰好是 \(a/2\)。否则,获胜玩家希望选择一个与 \(a\) 模 \(4\) 同余的最大素数。如果该素数为 \(p\),那么游戏将需要 \(1+(a-p)/2\) 回合。
然后跑埃氏筛就可以了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e6+100;
bool sg[N];
int max_mod4[N],min_turns[N];
void init()
{
max_mod4[0]=2,max_mod4[1]=1,max_mod4[2]=2,max_mod4[3]=3;
min_turns[0]=0,min_turns[1]=1;
for(int i=2;i<N;i++)
{
if(!sg[i])
{
for(int j=i;j<N;j+=i)
sg[j]=true;
max_mod4[i%4]=i;
}
min_turns[i]=(i-max_mod4[i%4])/2+1;
}
return;
}
int n;
void work()
{
cin>>n;
int ans=N;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
if(min_turns[x]/2<ans/2) ans=min_turns[x];
}
if(ans&1) return cout<<"Farmer John"<<endl,void();
return cout<<"Farmer Nhoj"<<endl,void();
}
signed main()
{
freopen("barn.in","r",stdin);
freopen("barn.out","w",stdout);
init();
int T;
cin>>T;
while(T--) work();
return 0;
}
一些小问题
小小的CE小问题
碰到了一种编译问题,在本地运行时合法的,并且可以得到正确答案,但是在 洛谷 或者 123OJ 上交就是CE。
CE截图如下:
或者
然后我去分析原因,发现我的代码里面有一个对全局数组赋初值的操作。
虽然但是我并不知道这到底是什么问题,但是改成这样就可以通过了。
虽然现在还不知道是什么原理,但是为了防止莫名其妙的CE,以后还是不要这样定义数组了。。。
C++强制提升栈空间
这个问题在NOIP2024的时候就碰到了,今天又碰到了一次。可能在CCF的机子上可以跑,但是你自己跑大样例的时候可以很方便看。
Dev-cpp 手动开栈:
工具 → 编译选项 → 编译器选项 → 在编译时加入如下命令(这里记得打 √ )
在框里添加相关命令即可(134217728=128∗1024∗1024即128MB的空间。)。
-Wl,--stack=134217728