8.18考试总结(NOIP模拟43)[第一题·第二题·第三题·第四题]
愿你和重要的人,在来日重逢。
前言
题目名字起的很随意。。。
这天 Luogu 的运势好像是大凶(忌:打模拟赛,注意报零)。
但是考得还不错,拿到了这么多场模拟赛以来第二三个场上AC。
所以说,我爱大凶
T1 第一题
解题思路
官方题解好像不干人事,直接咕了。。
其实做法都差不多,都是乱搞(反正我是这么干的)。
对于整棵树上的每一个点开一个 multiset
,用来维护子树内剩余的每一个士兵的深度。
然后每一次进行子树合并的时候选择深度最小的,但是到当前子树根节点距离的两倍小于根节点深度的点进行利用。
可以这么理解:把整个过程视作最后停留的节点的深度加上其它经过节点到 该节点与停留节点的LCA的距离的二倍(毕竟要下去再上来嘛)
然后就再开一个数组记录上面有来回的时间的贡献。
时间复杂度我不会算,好像是 n 的,但是我的 set 合并的时候都没有启发式合并,感觉最坏的情况会被搞成接近 \(n^2\)。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10,M=1e3+10;
int n,ans,fa[N],f[N],dep[N],mx[N];
vector<int> v[N];
multiset<int> s[N];
void add_edge(int x,int y)
{
v[x].push_back(y);
}
void dfs(int x)
{
mx[x]=dep[x];
for(int i=0;i<v[x].size();i++)
{
int to=v[x][i];
if(to==fa[x]) continue;
fa[to]=x;
dep[to]=dep[x]+1;
dfs(to);
mx[x]=max(mx[x],mx[to]);
}
}
void merge(multiset<int> &a,multiset<int> &b)
{
for(auto it=b.begin();it!=b.end();it++)
a.insert((*it)+1);
}
bool comp(int x,int y)
{
return mx[x]<mx[y];
}
void solve(int x)
{
bool flag=false;
sort(v[x].begin(),v[x].end(),comp);
for(int i=0;i<v[x].size();i++)
{
int to=v[x][i];
if(to==fa[x]) continue;
flag=true; solve(to);
for(auto it=s[x].begin();it!=s[x].end();it++)
{
if((*it)>=dep[x]||!s[to].size()) break;
auto it2=(--s[to].end());
if((*it2)+1<(*it)) break;
f[x]+=(*it)*2;
s[to].erase(it2);
s[x].erase(it);
s[x].insert((*it2)+1);
}
f[x]+=f[to];
merge(s[x],s[to]);
}
if(!flag) s[x].insert(0);
}
signed main()
{
n=read();
for(int i=1,x,y;i<n;i++)
{
x=read(); y=read();
add_edge(x,y);
add_edge(y,x);
}
dfs(1);
solve(1);
for(auto it=s[1].begin();it!=s[1].end();it++)
ans+=(*it);
printf("%lld",ans+f[1]);
return 0;
}
T2 第二题
解题思路
做法依旧非常暴力。(但是好像别的做法也可以被极端数据卡掉,比如n=1,m=1e5
)
一眼看出是二分答案。
二分最小的差值,判断是否可以用 \(\le k\) 的步数实现。
用一种类似于 SPFA 的方法实现判断。
为了保证每一个点都被扫到,先把所有的点都放到队列里面。
然后扫描每一个点周围的点的最大值,看看差值是否符合条件,不符合的话就更改并计入贡献。
接下来再次扫描周围的六个点,对于不合法的更改计入贡献,对于更改过但是不再队列里的入队。
最后把贡献和与 k 比较就好了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e3+10,M=1e5+10,INF=1e18;
int n,m,K,minn=INF,maxn;
int d1[10]={0,0,0,1,-1,1,-1};
int d2[10]={0,1,-1,0,0,-1,1};
bool vis[M];
vector<int> s[M],p[M];
pair<int,int> id;
struct Queue
{
int l,r;
pair<int,int> num[M<<2];
bool empty(){return l>r;}
pair<int,int> front(){return num[l];}
void clear(){l=1;r=0;}
void push(pair<int,int> temp){num[++r]=temp;}
void pop(){l++;};
}q;
int pos(int x,int y)
{
return (x-1)*m+y;
}
bool check(int mid)
{
memset(vis,true,sizeof(vis));
int sum=0; q.clear();
for(int i=1;i<=n;i++)
{
p[i]=s[i];
for(int j=1;j<=m;j++)
q.push(make_pair(i,j));
}
while(!q.empty())
{
int x=q.front().first,y=q.front().second;
q.pop(); vis[pos(x,y)]=false;
int maxn=0;
for(int i=1;i<=6;i++)
{
int x2=x+d1[i],y2=y+d2[i];
if(x2<=0||y2<=0||x2>n||y2>m) continue;
maxn=max(maxn,p[x2][y2]);
}
if(maxn-p[x][y]>mid)
{
sum+=maxn-p[x][y]-mid;
p[x][y]=maxn-mid;
}
if(sum>K) return false;
for(int i=1;i<=6;i++)
{
int x2=x+d1[i],y2=y+d2[i];
if(x2<=0||y2<=0||x2>n||y2>m) continue;
if(abs(p[x][y]-p[x2][y2])>mid)
{
sum+=p[x][y]-p[x2][y2]-mid;
p[x2][y2]=p[x][y]-mid;
if(!vis[pos(x2,y2)]) q.push(make_pair(x2,y2)),vis[pos(x2,y2)]=true;
}
}
if(sum>K) return false;
}
return sum<=K;
}
signed main()
{
n=read(); m=read(); K=read();
for(int i=1;i<=n;i++) s[i].push_back(0);
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++)
{
x=read();
s[i].push_back(x);
minn=min(minn,s[i][j]);
if(maxn<=s[i][j]) id=make_pair(i,j);
maxn=max(maxn,s[i][j]);
}
int l=0,r=maxn-minn,ans=maxn-minn;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)){r=mid-1;ans=mid;}
else l=mid+1;
}
printf("%lld",ans);
return 0;
}
T3 第三题
解题思路
看出来是数位 DP 了,奈何我太弱,不会。。
\(f_{i,j,l,r}\) 表示当先考虑到第 i 位,已经填上了 j 个 1,是否压紧上下界。
储存两个值:当前状态的数字个数,以及所有数字的总和。
为了方便我们计算 a 到 INF 以及 b 到 INF 的值,最后减一下就好了。
先记忆化DFS一遍算出限制内合法的个数。
然后就类似于查询排名,根据第一维的数字个数求出合法的数字总和。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int Lg=30;
struct Node
{
int x,y;
}f[Lg+10][Lg+10][2][2];
int T,li,ri,a,b;
Node dfs_unlim(int i,int j,bool l,bool r)
{
if(i<0||j<0) return (Node){0ll,0ll};
if(~f[i][j][l][r].x) return f[i][j][l][r];
int lim1=l&(li>>i),lim2=r&(!((ri>>i)&1));
Node ans1=(Node){0ll,0ll},ans2=(Node){0ll,0ll};
if(!lim1) ans1=dfs_unlim(i-1,j,(!((li>>i)&1))&l,(!((ri>>i)&1))&r);
if(!lim2) ans2=dfs_unlim(i-1,j-1,(li>>i)&l,(ri>>i)&r);
return f[i][j][l][r]=(Node){ans1.x+ans2.x,ans1.y+ans2.y+ans2.x*(1<<i)};
}
Node dfs_lim(int i,int j,bool l,bool r,int rk)
{
if(i<0||j<0||!rk) return (Node){0ll,0ll};
if(~f[i][j][l][r].x&&f[i][j][l][r].x<=rk) return f[i][j][l][r];
int lim1=l&(li>>i),lim2=r&(!((ri>>i)&1));
Node ans1=(Node){0ll,0ll},ans2=(Node){0ll,0ll};
if(!lim1) ans1=dfs_lim(i-1,j,(!((li>>i)&1))&l,(!((ri>>i)&1))&r,rk);
if(!lim2) ans2=dfs_lim(i-1,j-1,(li>>i)&l,(ri>>i)&r,rk-ans1.x);
return (Node){ans1.x+ans2.x,ans1.y+ans2.y+ans2.x*(1<<i)};
}
int work(int rk)
{
int sum=0;
for(int i=0;i<=Lg;i++)
if(rk>=f[Lg][i][1][1].x)
{
if(!(~f[Lg][i][1][1].x)) continue;
rk-=f[Lg][i][1][1].x;
sum+=f[Lg][i][1][1].y;
}
else
{
sum+=dfs_lim(Lg,i,1,1,rk).y;
break;
}
return sum;
}
int solve()
{
li=read(); ri=read(); b=read(); a=read();
a=ri-li+1-a+1; b=ri-li+1-b+1;
memset(f,-1,sizeof(f));
f[0][0][0][0]=f[0][0][0][1]=(Node){1ll,0ll};
f[0][0][1][0]=f[0][0][1][1]=(Node){!(li&1),0ll};
f[0][1][0][0]=f[0][1][1][0]=(Node){1ll,1ll};
f[0][1][0][1]=f[0][1][1][1]=(Node){ri&1,ri&1};
for(int i=1;i<=Lg;i++)
f[Lg][i][1][1]=dfs_unlim(Lg,i,1,1);
return work(b)-work(a-1);
}
signed main()
{
T=read();
while(T--) printf("%lld\n",solve());
return 0;
}
T4 第四题
解题思路
题面描述的不太清楚,我说一下自己的理解。
对于一个序列,保证每一位都是在 \([1,n]\) 范围内的,并且前 i 位的最大值等于 前 i-1 位的最大值,或者只比它多 1 。(这个序列并不一定是 \(1\sim n\) 的排列)
\(f_{i,j}\) 表示选择 i 个数最大的数字为 j 的方案数。
\(g_{i,j}\) 表示在最大值为 j 的序列后再选择 i 个数的方案数。
这两个方程可以互补求出整个序列的值。
因为\(k^2=C_{k}^{2}\times 2 + k\),所以每一种方案的贡献是 1+后面再选一个 x 的方案数 \(\times 2\)
可以的得出在第 i 个位置的 x 的贡献就是:
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=3e3+10;
int n,mod,f[N][N],g[N][N],s[N][N],ans[N];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
signed main()
{
n=read(); mod=read();
f[0][0]=f[1][1]=1;
for(int i=1;i<=n;i++)
g[0][i]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
add(f[i][j],(f[i-1][j]*j%mod+f[i-1][j-1])%mod);
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i;j++)
add(g[i][j],(g[i-1][j]*j%mod+g[i-1][j+1])%mod);
for(int i=1;i<=n;i++)
for(int j=i;j>=1;j--)
add(s[i][j],(f[i-1][j]*(g[n-i][j]+2*(n-i)*g[n-i-1][j]%mod)%mod+s[i][j+1])%mod);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
add(ans[j],(s[i][j]+f[i-1][j-1]*(g[n-i][j]+2*(n-i)*g[n-i-1][j]%mod))%mod);
for(int i=1;i<=n;i++)
printf("%lld ",(ans[i]+mod)%mod);
return 0;
}