20260814 - 杂题一锅炖 总结
前言
mmt 真好啊不给我们加题,AK 快乐。
mmt 真坏啊给我们做 \(2600\),这是能做的吗?
可爱的嘎嘎,但是没有喵

水 中 的 鸭 子 的 含 金 量 还 在 上 升

没人觉得这个很神圣吗?都来用吧,很适合破绷啊!!!
mmt belike:看起来我要把你的备注改成小黄鸭了(?
A - Grandma Capa Knits a Scarf
因为只有 \(26\) 个字母,所以枚举删掉哪个字母,先尝试把所有这个字母都删掉,如果不是回文串就不可能了,否则再算一下还能加回来多少这个字母,删掉的个数取 \(\min\) 即可。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
int T,n,p[N],Ans;
string s,t;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
T=read();
while(T--){
n=read();
cin>>s;s=" "+s;
Ans=n+1;
for(int x=0;x<26;x++){
t=" ";int cnt=0;
for(int i=1;i<=n;i++)
if(s[i]-'a'!=x)t+=s[i],p[++cnt]=i;
bool flag=1;
for(int i=1,j=cnt;i<j;i++,j--)
if(t[i]!=t[j])flag=0;
if(!flag)continue;
//cout<<t<<"!\n";
int res=0;
p[0]=0,p[cnt+1]=n+1;
//cout<<cnt<<"\n";
for(int i=1,j=cnt;i<=j;i++,j--){
//cout<<"res+=2*("<<min(p[i]-p[i-1],p[j+1]-p[j])<<"-1)\n";
res+=2*max(0,min(p[i]-p[i-1],p[j+1]-p[j])-1);
if(i+1==j){
//cout<<"res+="<<p[j]-p[i]-1<<"\n";
res+=p[j]-p[i]-1;
}
}
Ans=min(Ans,n-cnt-res);
}
cout<<(Ans>n?-1:Ans)<<"\n";
}
return 0;
}
/*
bbbbabbababbabbbabbbb
4 a 2a1a2 a 3 a 4
*/
B - Jumping Through Segments
发现这个东西具有单调性,越小的 \(k\) 越不可能完成关卡,而如果一个 \(k\) 能完成,则更大的也能完成,一个 \(k\) 不能完成,则更大的也不能完成。
所以可以二分,二分的时候维护一个区间,左端点往左跑,右端点往右跑,和当前限制线段区间取交集,只要走到最后一直都有交集则可以完成关卡,否则不能。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
int T,n,x[N],y[N],l,r,Ans;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
bool check(int k){
int L=0,R=0;
for(int i=1;i<=n;i++){
L-=k,R+=k;
L=max(L,x[i]),R=min(R,y[i]);
if(L>R)return 0;
}return 1;
}
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;i++)
x[i]=read(),y[i]=read();
l=0,r=1e9+5,Ans=1e9+5;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))Ans=mid,r=mid-1;
else l=mid+1;
}cout<<Ans<<"\n";
}
return 0;
}
C - Infected Tree
每次救下来的都只是该节点内子树(不含该节点)的个数。对于病毒蔓延到当前节点的时候,如果只有一个儿子就选择将其删除救下子树,如果有两个儿子,则可以往一边蔓延并救下另一边。显然无法快速判断哪边最优,但是都考虑一遍不会 T,所以 DFS 就可以解决问题了。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 3e5+5;
int T,n,sz[N];
vector<int> g[N];
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
void DFS_init(int u,int fa){
sz[u]=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v^fa)DFS_init(v,u),sz[u]+=sz[v]+1;
}return;
}
int DFS_sol(int u,int fa){
int c111=2-(u==1);
//cout<<u<<":";
if(g[u].size()<c111)return 0;
if(g[u].size()==c111){
int xx=g[u][0];
if(xx==fa)xx=g[u][1];
//cout<<"aaa\n";
return sz[xx];
}
int xx=g[u][0],yy=g[u][1];
//cout<<"wu?";
if(xx==fa)xx=g[u][2];
if(yy==fa)yy=g[u][2];
//cout<<"nana\n";
return max(sz[xx]+DFS_sol(yy,u),sz[yy]+DFS_sol(xx,u));
}
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;i++)g[i].clear();
for(int i=1;i<n;i++){
int x=read(),y=read();
g[x].pb(y),g[y].pb(x);
}
DFS_init(1,0);
//for(int i=1;i<=n;i++)cout<<sz[i]<<" ";cout<<"\n";
//cout<<"ok\n";
cout<<DFS_sol(1,0)<<"\n";
}
return 0;
}
D - Array Destruction
一个很重要的性质就是发现每次选择要删的两个数中一定要包含当前数列的最大值,否则最大值之后就再也没有机会被删掉了。一个很重要的观察是 \(n \le 1000\),可以 \(O(n^2)\) 搞定,那么我们考虑枚举第一次删点是选择最大值和哪个元素,这是 \(O(n)\) 的,里面再开桶 \(O(n)\) 模拟一遍,如果成功了就输出答案即可。
不建议使用什么 multiset,没必要,而且有用 multiset 做的调了整场最后成功没 AK 的小朋友,这告诉我们还是用桶吧 /lengh
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e3+5;
const int V = 2e6+5;
int T,n,a[N],t[V];
bool Can;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
T=read();
while(T--){
n=2*read();
for(int i=1;i<=n;i++)a[i]=read(),t[a[i]]=0;
sort(a+1,a+n+1);
Can=0;
for(int x=1;x<n;x++){
//第一次选 a[x] 与 a[n] 匹配
for(int i=1;i<=n;i++)t[a[i]]=0;
for(int i=1;i<=n;i++)t[a[i]]++;
int X=a[x]+a[n],mx=n;
bool flag=1;
vector<pii> f;f.clear();
for(int pp=1;pp<=n/2;pp++){
t[a[mx]]--;
if(X<=a[mx]||!t[X-a[mx]]){
flag=0;break;
}
t[X-a[mx]]--;
f.pb(make_pair(a[mx],X-a[mx]));
X=a[mx];
while(mx>0&&!t[a[mx]])mx--;
}
if(flag){
Can=1;
cout<<"YES\n";
cout<<a[n]+a[x]<<"\n";
for(int i=0;i<n/2;i++)
cout<<f[i].fr<<" "<<f[i].se<<"\n";
break;
}
}
if(!Can)cout<<"NO\n";
for(int i=1;i<=n;i++)t[a[i]]=0;
}
return 0;
}
E - Numerical Sequence (easy version)
非常简单的一个题目啊,大概就是首先发现因为 \(k\) 只有 \(10^9\) 所以最多涉及到前 \(10^5\) 个段的情况(事实上没有这么多,精确值我没算过),那么可以开两个数组 \(cnt\) 和 \(sum\),分别表示第 \(i\) 个段的长度,以及前 \(i\) 个段的长度之和。发现 \(cnt\) 就是每个数的长度的前缀和,而 \(sum\) 就是 \(cnt\) 的前缀和,因此这些全部可以快速预处理得到。
那么询问就简单了,我们先二分 \(sum\) 数组找出第 \(k\) 个数码属于哪个段,再二分 \(cnt\) 数组找出属于该段中的哪个数字,最后把找到的这个数字拆位求一下即可。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
const int up = 100000;
int Q;
LL cnt[N],sum[N];
LL read(){
LL su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
LL Count(int num){
int x=0;
while(num)x++,num/=10;
return x;
}
int main(){
Q=read();
for(int x=1;x<=up;x++)
cnt[x]=cnt[x-1]+Count(x),
sum[x]=sum[x-1]+cnt[x];
while(Q--){
LL k=read();
LL id=lower_bound(sum+1,sum+up+1,k)-sum;
k-=sum[id-1];
LL pid=lower_bound(cnt+1,cnt+up+1,k)-cnt;
k-=cnt[pid-1];
vector<int> f;f.clear();
int num=pid;
while(num)f.pb(num%10),num/=10;
cout<<f[f.size()-k]<<"\n";
}
return 0;
}
/*
计算第 i 个块含有 cnt[i] 个字符
求一个前缀和
就可以定位到是第几个块的第几个字符(可以二分)
因为一些奇奇妙妙的原因只会涉及到前 1e5 个块
然后枚举即可
*/
然后再额外讲一下 E2 的做法吧,比较粗糙。大概就是这个 \(k\) 的数据范围有了 \(10^{18}\) 了,这个时候会涉及到前 \(10^9\) 个段的情况了,原来的数组做法倒闭了,但我们发现只要在给定 \(i\) 的情况下可以快速算出 \(cnt_i\) 或 \(sum_i\),再套上 E1 的板子,问题也就迎刃而解了。然后推推式子就可以解决前面说到的那个问题啦,因为比较显然这边就不多说了,看代码吧。
戳这里看代码

呃,放错了,重来!
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
const int up = 100000;
int Q;
LL cnt[N],sum[N];
LL read(){
LL su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
LL Count(int num){
int x=0;
while(num)x++,num/=10;
return x;
}
LL Cnt(LL num){
LL now=1,res=0;
for(LL i=1;i<=18;i++){
now*=10ll;
res+=(min(now-1,num)-now/10+1)*i;
if(now>num)break;
}return res;
}
LL Sum(LL num){
LL now=1,res=0;
for(LL i=1;i<=18;i++){
now*=10ll;
LL l=now/10,r=min(now-1,num);
LL cnt=(num-l+1+num-r+1)*(r-l+1)/2;
res+=cnt*i;
if(now>num)break;
}return res;
}
LL Find_sum(LL num){
LL l=1,r=1e9,res=1e9;
while(l<=r){
LL mid=(l+r)>>1;
if(Sum(mid)>=num)res=mid,r=mid-1;
else l=mid+1;
}return res;
}
LL Find_cnt(LL num){
LL l=1,r=1e9,res=1e9;
while(l<=r){
LL mid=(l+r)>>1;
if(Cnt(mid)>=num)res=mid,r=mid-1;
else l=mid+1;
}return res;
}
int main(){
Q=read();
while(Q--){
LL k=read();
LL id=Find_sum(k);
k-=Sum(id-1);
LL pid=Find_cnt(k);
k-=Cnt(pid-1);
vector<int> f;f.clear();
int num=pid;
while(num)f.pb(num%10),num/=10;
cout<<f[f.size()-k]<<"\n";
}
return 0;
}
F - Money Buys Less Happiness Now
这题绝对没有 2000 啊,标难度的那个人应该是打错字了,我觉得是 1000。三分半秒切。
直接排序贪心是错的,因为可能钱还没攒够。所以我们从小到大考虑,每次先不管钱够不够用就买当月的幸福值,然后如果钱数变成负数了,就把前面买过的中最贵的那一个收回来(不买了),这样钱一定又会变成非负数(因为收回来的那个不会小于花费的价值,而这一次花费钱的钱数一定是非负数)。每月定时累加工作到账金额即可。具体实现用一个优先队列维护前面所有买过的幸福值的价格,是大根堆,以便每次取出最大值并弹出。最后的答案就是优先队列的长度(或者说大小)。
戳这里看代码

实在抱歉又放错了,下面这个才是代码!
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
int T,n,X,a[N];
priority_queue<int> q;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
T=read();
while(T--){
n=read(),X=read();
while(!q.empty())q.pop();
int now=0;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++,now+=X){
q.push(a[i]),now-=a[i];
if(now<0)now+=q.top(),q.pop();
}cout<<q.size()<<"\n";
}
return 0;
}
总结
没啥总结的,挺不错!
明天是什么捏?
仔细阅读有彩蛋!你绷住了吗?
感谢二位嘉宾出演本次总结,谢谢配合!!


看到最后你笑了吗(

浙公网安备 33010602011771号