[35] (CSP 集训) CSP-S 模拟 5
T1 光
Hikari
好神秘这个题,我觉得我解法够神秘了结果是正解
考虑到这四个数虽然不能二分答案,但是它们的和是能二分答案的
因此对和做二分答案
然后问题变成了 check 怎么写
设和最小的答案为
注意到
那么后面俩咋办
注意到可以令
把题目里的四个限制移项,移项之后等式左边还剩四组
对于前两组,发现当
说明还是有单调性,继续对
后面两组没办法了,因为挂 floor,只能说看起来和前面两组差不多,其实并不是单调的,可以自己试试
所以充分发扬人类智慧,先判前两组限制,前两组过了说明离答案非常近了,所以直接在答案区间左右
最坏复杂度
非常卡常,记得常数优化
#include<bits/stdc++.h>
using namespace std;
int a,b,c,d;
//判断 (i1,i2,i3,i4) 是否合法
inline int check(int i1,int i2,int i3,int i4){
if(i3+(i4/2)<c-(i1/2)-(i2/4)){
return 1;//i3 小了
}
if(i4+(i3/2)<d-(i1/4)-(i2/2)){
return -1;//i3 大了
}
if((i3/2)+(i4/4)<a-i1-(i2/2)){
return 0;//接神秘做法
}
if((i4/2)+(i3/4)<b-i2-(i1/2)){
return 0;
}
return 2;//正好是对的
}
//二分答案 i3
inline bool checksum2(int sum,int i1,int i2){
int remain=sum-i1-i2;
int l=0,r=remain;
while(l<=r){
int i3=(l+r)/2;
int i4=remain-i3;
int res=check(i1,i2,i3,i4);
if(res==2) return true;
if(res==1){
l=i3+1;
}
if(res==-1){
r=i3-1;
}
if(res==0){
for(int i=max(0,i3-8);i<=min(remain,i3+8);++i){
//暴力
if(check(i1,i2,i,remain-i)==2){
return true;
}
}
return false;
}
}
return false;
}
//二分答案 sum 的 check() 函数
inline bool checksum(int sum){
for(int i1=0;i1<=sum;++i1){
for(int i2=0;i2<=sum-i1;++i2){
if(i1==114 and i2==22){
}
if(checksum2(sum,i1,i2)){
return true;
}
}
}
return false;
}
signed main(){
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d %d %d %d",&a,&b,&c,&d);
int l=0,r=a+b+c+d,ans=-1;
//二分答案 sum
while(l<=r){
int mid=(l+r)/2;
if(checksum(mid)){
r=mid-1;
ans=mid;
}
else{
l=mid+1;
}
}
cout<<ans<<'\n';
}
/*
50 24 25 12
*/
B.爬
爬了
没用大家的思路,但是还是殊遇同归了
首先考虑计算在每个节点处形成的的答案值,发现这样的答案只会涉及到它本身与它的下一级节点,因此考虑全部提出来
提出来之后,这些所有数形成了一个序列,而你需要做的是算出所有子序列的异或和的和(不包括空序列与长度为
考虑算出来之后干什么,注意到不在这个范围内的节点进行移动是不会对这个范围内的答案造成影响的,因此直接将这个答案乘以
- 可以自由移动(即不能是根节点)
- 不是当前节点及其下一级子节点
这里需要额外注意的是统计根节点的答案时候的情况,注意不要多减
然后说怎么算这个所有子序列的异或和的和
对序列里的所有数拆二进制位,考虑第
值得注意的是根节点处的统计,根节点无法移出,意思是必须选
复杂度大概是线性乘以啥,里面复杂度不会算
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e9+7;
int n;
vector<int>e[100001];
int a[100001];
int power(int a,int t){
int base=a,ans=1;
while(t){
if(t&1){
ans=ans*base%p;
}
base=base*base%p;
t>>=1;
}
return ans;
}
//以下卢卡斯相关
long long fact[100001];
void dofact(){
fact[0]=1;
for(int i=1;i<=100000;++i){
fact[i]=fact[i-1]*i%p;
}
}
long long C(long long n,long long m){
if(!m){
return 1;
}
if(n<m){
return 0;
}
if(m>n-m){
m=n-m;
}
long long ans=fact[n]*power(fact[m],p-2)%p*power(fact[n-m],p-2)%p;
return ans;
}
long long lucas(long long n,long long m){
if(!m) return 1;
return lucas(n/p,m/p)*C(n%p,m%p)%p;
}
//存储 0,1 的个数
int cnt0[33],cnt1[33];
int solve2(vector<int>&val,bool limit){//limit==true 是根节点
memset(cnt0,0,sizeof cnt0);
memset(cnt1,0,sizeof cnt1);
//拆位
for(int p=0;p<=(int)val.size()-(limit?2:1);++p){
int i=val[p];
for(int j=1;j<=32;++j){
if(i&1){
cnt1[j]++;
}
else{
cnt0[j]++;
}
i>>=1;
}
}
int ans=0;
for(int i=1;i<=32;++i){
int res=0;
for(int j=((limit and ((val.back()>>(i-1))&1)==1)?0:1);j<=cnt1[i];j+=2){
res+=lucas(cnt1[i],j);
res%=p;
}
res=res*power(2,cnt0[i])%p;//0 的位置随便排
if(!limit or ((val.back()>>(i-1))&1)==1)res-=lucas(cnt1[i],limit?0:1);//减不合法
ans+=res*power(2,i-1);//该位贡献量
ans%=p;
}
return ans;
}
int dfs(int now,int last){
int res=0,fas=n-1-(now!=1);//fas 即为上述 s
vector<int>val;
for(int i:e[now]){
if(i!=last){
fas--;
res+=dfs(i,now);
res%=p;
val.push_back(a[i]);
}
}
val.push_back(a[now]);
int rrres=solve2(val,now==1);
res+=(rrres)%p*power(2,fas)%p;
return res%p;
}
signed main(){
freopen("climb.in","r",stdin);
freopen("climb.out","w",stdout);
dofact();
scanf("%lld",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
}
for(int i=2;i<=n;++i){
int x;scanf("%lld",&x);
e[x].push_back(i);
e[i].push_back(x);
}
cout<<dfs(1,0)%p;
}
C. 字符串
50 分 DP 是好写的
A
,B
,最后连续段长度为 A
(B
(
转移的时候直接填就行了,太简单了不赘述
#include<bits/stdc++.h>
using namespace std;
int t;
int n,m,a,b,c;
int f[101][101][101][2];//0: last is A 1: last is B
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d",&t);
while(t--){
scanf("%d %d %d %d %d",&n,&m,&a,&b,&c);
memset(f,0,sizeof f);
f[0][0][0][0]=f[0][0][0][1]=1;
for(int i=0;i<=n;++i){
for(int j=0;j<=m;++j){
for(int k=1;k<=i;++k){
if(i!=0 and j!=0) f[i][j][1][1]=max(f[i][j][1][1],f[i][j-1][k][0]+1);
}
for(int k=1;k<=j;++k){
if(i!=0 and j!=0) if(k>=c) f[i][j][1][0]=max(f[i][j][1][0],f[i-1][j][k][1]+1);
}
for(int k=1;k<=i;++k){
if(i!=0) f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j][k-1][0]+(k!=1 and (k-1)%a==0));
}
for(int k=1;k<=j;++k){
if(j!=0) f[i][j][k][1]=max(f[i][j][k][1],f[i][j-1][k-1][1]+(k!=1 and (k-1)%b==0));
}
}
}
int ans=0;
for(int i=0;i<=1;++i){
for(int j=0;j<=(i==0?n:m);++j){
ans=max(ans,f[n][m][j][i]);
}
}
cout<<ans<<'\n';
}
}
题解解析写挺好,那我就不用写了
我们可以枚举 A
和 B
切换了多少次,假定我们切换的方式就是 BBBABBBABBBA
,即每一段 A
的长度都为 A
B
的切换次数,就可以知道现在已经用掉了几个 A
,然后我们先考虑把 A
全部用完。
- 在
BBBABBBA
的形式前面 只需要花费一个A
就可以拿到一个价值 - 然后将多余的
A
填充进字符串中,因为每一段A
的长度都是 ,所以此时本质上是每多 个A
,答案 。
然后再考虑将剩余的 B
用完。
- 如果倒数第二个位置是
A
的话,那么最后一个只需要花费一个B
就能拿到一个价值 - 例如
,本来我们填充的是BBBABBBABBBA
,根据规则 2,当一段B
的长度达到 的时候又可以使得价值 ,所以我们尽量将每一段B
都填充到长度为 。如果全部填充好了且还有多余的 ,那么每多 个 ,答案 。
一些细节在代码里贴了注释,如果你能看懂我在说啥的话
#include<bits/stdc++.h>
using namespace std;
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int t;
cin>>t;
while(t--){
int n,m,a,b,c;
cin>>n>>m>>a>>b>>c;
int maxn=0;
for(int i=0;i<=min(n,m/c);i++){
//i stands for the switch times
//we need to put c 'b' together to switch to a,so there is a m/c
//and this min means the max time we can switch
int sum=0;
sum+=i*2-1;
//this -1 means the switch at begin, bacause the begin switchness only 1 times but not 2
//i*2 is the switch score totally, *2 because there both a to b and b to a
//we do not care the last switch if it is not the same as the first switch
if(n-i>=1){
sum+=1+(n-i-1)/a;
//except i a that used to switch, put others a together and insert after one a
//+1 because we can insert a 'a' before B...BAB...BAB...BA
//-1 because we used one to +1
}
if(c>b){
sum+=(c-1)/b*i;
//cals if a area lengths c make any score
}
//bacause i play genshin impact
if(m-c*i>=1){
//try to make every B area lengths to b+1
int x=m-i*c-1;//remain B
int mod=(((c-1)/b+1)*b)-(c-1); //needs to be next b+1
if(x/mod>=i){
sum+=i;
x-=mod*i;
}
else{
sum+=x/mod;
x-=x/mod*mod;
}
sum+=1+x/b;
//x/b because if we have too much b, we can make it at back to make scores
}
maxn=max(maxn,sum);
}
cout<<maxn+1<<'\n';
}
}
D. 奇怪的函数
感谢 Qyun 提供的思路
奇怪的分块妹妹助我过奇怪的函数题
关于为什么这玩意一定是一个三段函数
初始时,有
加和操作,使函数整体平移,形态不变
因此是三段函数
考虑怎么求拐点
求拐点的实质是维护最大最小值,由于整体加和操作会导致图像整体平移,不妨设
由于我们已经定义了偏移量,所以最大最小值只需要扫一遍取
有一些问题,
如果新加入的
这是预处理的一些细节
其余直接对操作分块即可,在每个块内分别求值,求出来带进下一个块内
注意由于真实的
预处理一定要倒着做,后赋的值比先赋的值优先级要高
#include<bits/stdc++.h>
using namespace std;
int n,q;
class huge{
public:huge(){}
operator int(){return 0x3f3f3f3f;}
}blublu;
struct operat{
int id;
int vali;
}op[100001];
int len;
struct area{
int minn,maxn,dx;
}a[100001];
huge act(int id){
a[id]={-blublu,blublu,0};
for(int i=min(n-1,(id+1)*len-1);i>=id*len;--i){
if(op[i].id==1){
a[id].dx+=op[i].vali;
a[id].maxn-=op[i].vali;
a[id].minn-=op[i].vali;
}
if(op[i].id==2){
a[id].maxn=min(a[id].maxn,max(a[id].minn,op[i].vali));
}
if(op[i].id==3){
a[id].minn=max(a[id].minn,min(a[id].maxn,op[i].vali));
}
}
return huge();
}
huge prework(){
len=sqrt(n);
for(int i=0;i<=(n-1)/len;++i){
act(i);
}
return huge();
}
int solve(int id,int x){
if(x<=a[id].minn) return a[id].minn+a[id].dx;
if(x>=a[id].maxn) return a[id].maxn+a[id].dx;
return x+a[id].dx;
}
int f(int x){
for(int i=0;i<=(n-1)/len;++i){
x=solve(i,x);
}
return x;
}
int main(){
freopen("function.in","r",stdin);
freopen("function.out","w",stdout);
cin>>n;
for(int i=0;i<=n-1;++i){
cin>>op[i].id>>op[i].vali;
}
prework();
cin>>q;
while(q--){
int _op,pos,val,x;
cin>>_op;
if(_op==1){
cin>>pos>>val;pos--;
op[pos].id=1;
op[pos].vali=val;
act(pos/len);
}
if(_op==2){
cin>>pos>>val;pos--;
op[pos].id=2;
op[pos].vali=val;
act(pos/len);
}
if(_op==3){
cin>>pos>>val;pos--;
op[pos].id=3;
op[pos].vali=val;
act(pos/len);
}
if(_op==4){
cin>>x;
cout<<f(x)<<'\n';
}
}
}
/*
10
1 48
1 50
1 180
2 957
1 103
1 100
1 123
3 500
1 66
1 70
3
4 20
4 50
4 700
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!