周报2
补题1:[蓝桥杯2013国AC]网络寻路
题意:找出包含四个结点的路径条数,源结点和终结点可以相同,但中间节点必须不同。
做法:dfs暴力搜简单易想,但是会TLE。另一种巧妙的做法,枚举每一条边(最多1e5条),固定每一条边,ans+=(du[u]-1)*(du[v]-1)*2;即为答案。一条边固定两个端点,剩下两个端点在相互配对,又因为有正反向,所以乘2。
void solve(){ //D 补题,较为巧妙的想法,枚举每一条边,两个端点的度数相乘再乘二(正反向); 时间复杂度o(m)
int n,m,ans=0;
cin>>n>>m;
int du[10004]={0};
vector<pair<int,int>> edge;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
du[u]++,du[v]++;
edge.emplace_back(u,v);
}
for(auto e:edge){
ans+=(du[e.first]-1)*(du[e.second]-1)*2;
}
cout<<ans<<endl;
}
补题2:[蓝桥杯2022国B]搬砖
题意:n块砖,重量为w,价值为i。从这些砖中选一些出来从下到上堆成一座塔,并且对于塔中的每一块砖来说,它上面所有砖的重量和不能超过它自身的价值。这样堆成的塔的总价值(即塔中所有砖块的价值和)最大是多少。
做法:找到排序方法,01背包,从上往下dp。
for(int i=1;i<=n;i++){ //排序后从上往下第i个物品
for(int j=arr[i].w+arr[i].v;j>=arr[i].w;j--){
//j是可放重量;j从arr[i].w+arr[i].v开始是因为这样可以保证符合条件的上一个物品可以被取到,到arr[i].w止是再小就不能放i物品了
//样例:2 10-9 20-11
dp[j]=max(dp[j],dp[j-arr[i].w]+arr[i].v);
ans=max(dp[j],ans);
}
}
补题3:[蓝桥杯2014省AB]
题意:给定一个n*m的矩阵,每个格子都有价值不等的物品。入口在左上角,出口在右下角。只能往右或往下走。走过某个格子时,如果那个格子中的宝贝价值比手中任意宝贝价值都大,则可以拿起它(也可以不拿)。有多少种不同的行动方案能获得k 件宝贝。
过程:暴力dfs-->记忆化搜索(AC)-->dp。。。
补题4:Setsuna的K数列
int mod=1e9+7;
void solve(){ //D 想到了是k进制相关,就是没发现K数列本质就是每一个K进制位只能取0或1,即第n个数就是把n表示成二进制然后按K进制还原即可
int n,k;
cin>>n>>k;
int ans=0,cur=1;
while(n){
if(n&1) ans=(ans+cur)%mod;
cur=cur*k%mod;
n>>=1;
}
cout<<ans<<endl;
}
反思:赛时一直在找规律,找规律,期间也发现了K进制这个特点。的确是有些规律,但是陷进一个错误的思维里面。就是没发现K数列本质就是每一个K进制位只能取0或1,即第n个数就是把n表示成二进制然后按K进制还原即可。
补题5:剪绳子
题意:在0到10的数轴上,输入有两种操作:A x;C x;A是询问x所在的线段多长;C是从x点剪开数轴。Cx不会是0或10;Ax不会是Cx中出现过的,但是可能是0或10;对于每一次A询问,输出长度。
做法有两种: 第一种是STL+二分:维护一个set,里面存的是操作中C的点。遇到A时,通过二分在set中找到第一个>=x的数,和第一个>x的数。因为没有比10大的数,特判一下A 10的情况即可。
void solve(){ //L 剪绳子--STL 法①并查集 法②set--巧妙
cout<<fixed<<setprecision(5);
int n;
cin>>n;
set<double> st;
st.insert(0),st.insert(10); //两个端点
char op;
double num;
while(n--){
cin>>op>>num;
if(op=='A'){
auto pos1=lower_bound(st.begin(),st.end(),num); //第一个>=num的pos
auto pos2=upper_bound(st.begin(),st.end(),num); //第一个>num的pos
if(pos2==st.end()) cout<<10-*(--pos1)<<endl; //特例!!
else cout<<(*pos2)-*(--pos2)<<endl;
}
if(op=='C') st.insert(num);
}
}
第二种是并查集:这个方法比较麻烦,写了一下而且最终也没能AC。因为输入有五位小数,可以把他们都扩大1e5倍,成为一个整数。先要把输入的数据全部读入保存。 并且用map把Cx的端点都标记一下。再从0到1000000,遍历,把线段分为各自集合,并且记录每个集合大小。接着再倒着遍历存了所有输入数据的数组,遇到Ax直接输出cnt[find(x)]。遇到Cx则Union(x-1,x);不知道是因为精度还是什么问题,写的代码只有60分;但是最主要这题收获的是要有这种把小数变正数,再使用并查集的想法,还有逆向思维的。
补题6:XOR-distance
题意:给出三个正数a,b,r;找出0<=x<=r,| (aXORx) - b(XOR)x |的最小值。
做法:这题是挺好的位运算练习题。a,b异或同一个数字,转到二进制其实就是找一个数字,这个数字要满足区间范围,而且从高位到低位给这个数字x填数。是填0还是1,取决于a,b在该位的二进制值。有个很重要的细节!!!就是1<<64会溢出,需要写成1ll<<64!!使用 (1ll<<k)
来计算一个大于 int 范围的位运算值。
void solve(){ //C 应该从高位到低位检查,而不是从低位到高位
// bitset<20> arr=a; //从低位到高位存的
// bitset<20> brr=b;
// string str1=arr.to_string(); //str1是高位到低位的
// string str2=brr.to_string();
// cout<<str1<<endl<<str2<<endl;
// str1=str1.substr(str1.find('1')); //去除多余的0
// str2=str2.substr((str2.find('1')));
// cout<<str1<<endl<<str2<<endl;
// for(int i=0;i<19;i++) cout<<arr[i];
// cout<<endl;
// for(int i=0;i<19;i++) cout<<brr[i];
int a,b,r;
cin>>a>>b>>r;
if(a>b) swap(a,b); //lit小big大
bitset<64> aa=a,bb=b;
string lit,big;
lit=aa.to_string(),big=bb.to_string();
int take=0;
for(int i=0;i<=63;i++){
if(lit[i]==big[i]) continue;
//int cur=(1<<(63-i));--wrong!!
int cur=(1ll<<(63-i)); //位运算的细节!!! 1<<64是不行的!! 需要1ll<<64
if(lit[i]=='0' && cur+take<=r && b-a>=2*cur){
b-=cur,a+=cur;
take+=cur;
}
}
cout<<b-a<<endl;
}
补题7:Did We Get Everything Covered?
题意:![](https://img2024.cnblogs.com/blog/3142690/202402/3142690-20240204140519401-1235857441.png)
做法:第一道题目的衍生,由第一道题目不难得到类似于(a1,a2,……ak)【先后顺序可变】的组合出现n次才能满足条件,如果不满足,那么肯定是(a1,a2,……ak)在某个序列中缺失了。现在还有一个问题就是,如果是NO,应该怎么确定缺了哪个序列?对于每一组(a1,a2,......,ak)如果选择每一组的a1期间其实可能穿插了很多重复的,再加上最后缺失的字母,可能不是正确答案。但是可以选择每一组最后出现的ak,这样可以避免a1,a2,......ak之间穿插的重复的字母会被选择到,保证每组的ak加上最后缺失的字母,一定是答案。
void solve(){ //C 错哪了?
// 第一题的衍生题,可以按照第一题的想法:由第一道题目不难得到类似于(a1,a2,……ak)【先后顺序可变】的组合出现n次才能满足条件,如果不满足,那么肯定是(a1,a2,……ak)在某个序列中缺失了。
// wa--3,2,6;abaabb;;ans=NO,bba;;but coutYES
// wa--3,2,7;aabbbaa;;ans=NO bab ;;but coutYES
//6 2 m;aaaaaaa bbbb a bbb a bb a b a a
int n,k,m;
cin>>n>>k>>m;
string str;
cin>>str;
vector<char> ans;
int idx=0,cnt=0;
int vis[26]={0};
while(idx<m){
for(int i=0;i<26;i++) vis[i]=0;
int curcnt=0;
for(int i=idx;i<m;i++){
idx=i+1;
if(str[i]-'a'>=0&&str[i]-'a'<k){
if(!vis[str[i]-'a']){
curcnt++;
if(curcnt==k){
ans.emplace_back(str[i]); //如果是NO,那么输出每组最右边的字符,再加上最后缺的n-ans.size()个字母
cnt++;
break;
}
vis[str[i]-'a']=1;
}
}
}
}
if(cnt>=n) cout<<"YES\n"; // >=
else{
cout<<"NO\n";
for(auto a:ans) cout<<a;
for(int i=0;i<k;i++){
if(!vis[i]){
char c='a'+i;
for(int j=1;j<=n-ans.size();j++) cout<<c; //可能缺了好几组
cout<<endl;
break;
}
}
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int t=1;
cin>>t;
while(t--) solve();
return 0;
}
总结:这周打了cf上的比赛,也打了牛客训练营1。cf上面打的还差把劲,C题都是差点写出来,但是赛时写不出来。牛客训练营1也是差把劲,按理来说应该至少到7题,但是才出了5题。基本上都是差一点点,但是就是写不出来。现在要提高的就是这一点点,尽量避免这种情况,要把能写的题都稳定写出来。补题方面也是规规矩矩,赛时没出的,不是非常超过自己能力范围的,基本上都去尽力补。再接再厉!