20261016 小测 补题
因为月考只打了 25 min,切完一道橙就走了。不得不不断补题。
T1 东风谷早苗
原题的人名就是这个。
题意概述
给定一个指令串 $ S $ ,与行动步数 $ T $ ,每一步都按照给定的指令串走,走到指令串的尽头就从指令串的最前面重新开始执行指令。每个指令让一个最开始位于 $ (0,0) $ 的机器人朝 ’E‘,’W‘,’N‘,’S‘ 四个方向走一步。问机器人最后的位置。
solution
一眼周期。对于每个周期,我们算出执行到这个周期的每一步会让 $ x $ 和 $ y $ 增加/减少多少,然后对于周期剩下的那部分直接加上变化量即可。
注意代码中对于剩下部分的处理。非常玄学,等我再研究。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005;
string s;
int t,x[N],y[N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>s>>t;
int len=s.size();
for(int i=0;i<len;i++){
x[i]=x[max(i-1,0ll)];
y[i]=y[max(i-1,0ll)];
if(s[i]=='N')y[i]++;
if(s[i]=='S')y[i]--;
if(s[i]=='E')x[i]++;
if(s[i]=='W')x[i]--;
}
int sumx=x[len-1],sumy=y[len-1];
sumx*=t/len;
sumy*=t/len;
t=max(t%len-1,0ll);
cout<<sumx+x[t]<<' '<<sumy+y[t];
return 0;
}
T2 Prawnicy
题意概述
给定一些区间,从中选择 $ k $ 个区间使得这些区间的交集最大。
solution
感觉像那种第一眼瞪不出来就别想切的题。神秘贪心。
将区间按照左端点排序,遍历每个左端点,然后用一个小根堆维护右端点,每次当小根堆的大小为 $ k $ 的时候找到最小的右端点然后计算这个最小的右端点和这个左端点的差即可。如果这个堆的大小大过 $ k $ 的话那么就直接弹出最小的右端点就好了,毕竟最小右端点肯定是最大最好。
也许你会问如果区间断开了怎么办。选中的区间有断开那么:
-
要么最小的 $ r $ 显然比当前的 $ l $ 小,这个时候算出来的答案是负数我们直接不计算。
-
要么断点在后面,这个时候交集就是当前点的 $ l $ 到当前点的 $ r $ ,这个时候正常计算即可。
至于输出具体方案,我们在贪心求解的时候记录最优区间,只需要随便找 $ k $ 个覆盖了最优区间的的区间即可。顺序不重要有 spj 。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000005;
int n,k,l,r;
struct lawyer{
int l;
int r;
int id;
}a[N];
bool cmp(lawyer a,lawyer b){return a.l<b.l;}
priority_queue<int,vector<int>,greater<int> >q;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i].l>>a[i].r,a[i].id=i;
int zd=0;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
q.push(a[i].r);
while(q.size()>k)q.pop();
if(q.size()==k){
if(q.top()-a[i].l>zd){
zd=q.top()-a[i].l;
l=a[i].l;
r=q.top();
}
}
}
cout<<zd<<'\n';
for(int i=1;i<=n;i++){
if(a[i].l<=l&&a[i].r>=r&&k){
cout<<a[i].id<<' ';
k--;
}
}
return 0;
}
T3 Kuglarz
题意概述
给你 $ n $ 个碗,每个碗下面有可能有一个球。你可以询问一些区间里球的数量的奇偶,但是也要花费对应的代价。问确定所有碗是否有球的最小代价。
solution
神题。
问 $ a_i+a_{i+1}+...+a_j $ 的奇偶,就是问 $ a_i+a_{i+1}+...+a_j \bmod 2 $ 的值。
注意到在模 2 意义下加法等同于异或。所以问 $ a_i+a_{i+1}+...+a_j $ 的奇偶就是问 $ a_i \oplus a_{i+1} \oplus ... \oplus a_j $ 。
看见区间异或容易想到前缀异或和。令 $ S_i $ 表示 $ a_1 \oplus a_2 \oplus ... \oplus a_i $ ,那么 $ a_i \oplus a_{i+1} \oplus ... \oplus a_j $ 就是 $ S_j \oplus S_{i-1} $ 。那么如果我们要确定每一个 $ a_i $ ,我们就必须知道每个 $ S_i $ 。
这个时候因为我们已知 $ S_0 $ 等于 0 ,如果我们可以通过一些询问让 $ S_0 $ 与 $ S_i $ 连起来,那么 $ S_i $ 自然就求出来了。
这个东西似乎比较像一个建模。对于每一个可能的询问,我们连一条边,代价就是题目给出的代价。如果我们要知道 $ S_i $ ,我们就必须要让 $ S_i $ 和 $ S_0 $ 连通。要求出所有 $ S_i $ 等价于让所有的$ S_{0,1...n} $ 连通。于是原问题就被我们转化成了最小生成树问题。
然后跑一下 Prim / Kruskal 就可以了。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int n,a[N][N];
bool vis[N];
struct EDGE{
int to;
int dis;
bool operator >(const EDGE &other)const{
return dis>other.dis;
}
};
priority_queue<EDGE,vector<EDGE>,greater<EDGE> >q;
vector<vector<EDGE> >g;
int prim(int st){
int cnt=0,res=0;
q.push({0,0});
while(cnt!=n+1){
EDGE p=q.top();
q.pop();
if(vis[p.to])continue;
cnt++;
vis[p.to]=1;
res+=p.dis;
for(EDGE v:g[p.to])q.push(v);
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
g.resize(n+1);
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
g[i-1].push_back({j,a[i][j]});
g[j].push_back({i-1,a[i][j]});
}
}
cout<<prim(0);
return 0;
}
T4 ~ T5
不会,先咕着。

浙公网安备 33010602011771号