20261008 小测总结
省流:借助群众的力量成功冲入前五。tql。
T1 寻找小汽车
找不到原题。
题意概述
砝码John在 $ x $ ,小汽车在 $ y $ ,砝码John每次先走到 $ x+1 $ , $ x-2 $ , $ x+4 $ , $ x-8 $ ...问砝码John要走几步才能找到他的小汽车。
Tips:砝码John是一步步在数轴上蠕动的,遇到汽车就停止。另外 $ x $ 不随砝码John的移动改变。
solution
原题面给我恶心到了,一个红题写了半天。。。
直接模拟即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>a>>b;
int k=1,cnt=0,ans=0,now=a;
while(1){
if(cnt%2==0){
ans+=a+k-now;
if(now<=b&&a+k>=b){
ans-=a+k-b;
cout<<ans;
exit(0);
}
now=a+k;
}
else{
ans+=now-a+k;
if(now>=b&&a-k<=b){
ans-=b-(a-k);
cout<<ans;
exit(0);
}
now=a-k;
}
k*=2;
cnt++;
}
return 0;
}
T2 新冠亮码
找不到原题。
题意概述
对于每个顾客有一个到达时间和一个服务时间,这个顾客到达后需要占用 ta 所需的服务时间来完成服务,问总共需要多少时间。
solution
-
维护一个当前时间。
-
如果顾客在当前时间之后到达,那么当前时间就是顾客的到达时间加上顾客的服务时间。
-
如果这个顾客需要等待到当前时间(上一个顾客服务结束)再开始接受服务,则将当前时间直接加上服务时间即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=105;
int n;
struct cus{
int s;
int t;
}a[N];
bool cmp(cus a,cus b){
return a.s<b.s;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].s>>a[i].t;
sort(a+1,a+1+n,cmp);
int tim=0;//当前时间
for(int i=1;i<=n;i++){
if(tim<a[i].s)tim=a[i].s;//不需要等待,当前时间延续到下一个顾客到来
tim+=a[i].t;//加上顾客的服务时间
}
cout<<tim;
return 0;
}
T3 策略前行
题意概述
给定一个平面直角坐标系和两个人的行动路线,对于每一个时刻这两个人可以选择不行动或者按照行动路线走一步。每一个时刻结束移动后结算代价(代价为两人的欧几里得距离的平方),问两个人走完各自路径的最小代价。
solution
注意到两个人同时不走一定不优。考虑 $ dp $ 。设 $ dp_{i,j} $ 表示一个人走 $ i $ 步而另一个走 $ j $ 步的最小代价。那么显然有 $ dp[0][0] = 0 $ ,转移则有 $ dp[i][j] = \min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + w $ ( $ w $ 表示当前的代价) ,也就是枚举有哪些人走。最后输出 $ dp[n][m] $ ( $ n , m $ 表示两个人的行动步数)即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n,m,dp[N][N];
struct pt{
int x;
int y;
}a[N],b[N];//两个人移动一定步数后的坐标
inline int dis(int xa,int xb,int ya,int yb){//代价
int cx=abs(xa-xb),cy=abs(ya-yb);
return cx*cx+cy*cy;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
char c;
int x,y;
cin>>x>>y;
int xa,ya;
cin>>xa>>ya;
a[0]={x,y};
for(int i=1;i<=n;i++){
cin>>c;
if(c=='N')y++;
if(c=='S')y--;
if(c=='E')x++;
if(c=='W')x--;
a[i]={x,y};
}
x=xa,y=ya;
b[0]={x,y};
for(int i=1;i<=m;i++){
cin>>c;
if(c=='N')y++;
if(c=='S')y--;
if(c=='E')x++;
if(c=='W')x--;
b[i]={x,y};
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i==0&&j==0)continue;
int w=dis(a[i].x,b[j].x,a[i].y,b[j].y);
if(i==0)dp[i][j]=dp[i][j-1]+w;
else if(j==0)dp[i][j]=dp[i-1][j]+w;
else dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+w;
}
}
cout<<dp[n][m];
return 0;
}
T4 视频推荐
题意概述
给定一个带权树和一些查询,每次查询给出一个 $ u $ 和一个 $ k $ 问所有除 $ u $ 以外的节点里有多少节点到 $ u $ 的路径上经过的最小边权大于等于 $ k $ 。
solution
因为如果从 $ u $ 出发到达一个 $ v $ ,途中有一个边的长度小于 $ k $ ,那么这个边往后的一整个连通块都是没用的,相当于这个边被删除了。
但是每次查询都删边硬统计显然是过不了的。于是我们考虑构造加边。
容易想到离线,然后将 $ k $ 从大到小处理,持续加入边权大于等于 $ k $ 边。用并查集维护连通块的大小,指定节点所在的连通块大小减一(因为自身不计入答案)就是答案了。
至于边权大于等于 $ k $ 的边,容易想到将边按边权从大到小排序,用一个指针标记加入了哪些边,每次 $ k $ 变小则指针后移直到边权不符合条件。
时间复杂度 $ O ( n + q ) $ 。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n,t,fa[N],sz[N],ans[N],res;
struct query{
int k;
int ved;
int ans;
int id;
}q[N];
struct edge{
int u;
int v;
int w;
}eg[N];
inline bool cmp(edge a,edge b){return a.w>b.w;}
inline bool cmpb(query a,query b){return a.k>b.k;}
inline bool cmpc(query a,query b){return a.id<b.id;}
inline int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
inline void merge(int a,int b){
int faa=find(a),fab=find(b);
fa[faa]=fa[fab];
sz[fab]+=sz[faa];
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>t;
for(int i=1;i<n;i++)cin>>eg[i].u>>eg[i].v>>eg[i].w;
for(int i=1;i<=t;i++)cin>>q[i].k>>q[i].ved,q[i].id=i;
for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
sort(eg+1,eg+1+n,cmp);
sort(q+1,q+1+t,cmpb);
int pos=1;
for(int i=1;i<=t;i++){
while(pos<n&&eg[pos].w>=q[i].k){
merge(eg[pos].u,eg[pos].v);
pos++;
}
q[i].ans=sz[find(q[i].ved)];
}
sort(q+1,q+1+t,cmpc);
for(int i=1;i<=t;i++)cout<<q[i].ans-1<<'\n';
return 0;
}
T5 字符合并
题意概述
有一个长度为 $ n $ 的 01 串,你可以每次将相邻的 $ k $ 个字符合并,得到一个新的字符并获得一定分数。给定一些合并方案,假定你合并了对应十进制值为 $ x $ 的一个 01 串,那么这个 01 串就会转化为方案 $ x $ 的对应字符并获得方案 $ x $ 的对应分数。求最大分数。
solution
(第一次写紫题题解,质量或许不高。。。)
注意到 $ k $ 很小,大概率是暴搜 / 状压。而看题目的操作大概是类似于石子合并这样的类似区间dp的东西。
于是我们可以往区间dp + 状压dp上想。
设 $ dp_{l,r,S} $ 表示把区间 $ l $ 到 $ r $ 合并成 $ S $ 的最大分数。初始化把 $ dp $ 数组设置为 $ -INF $ 。
然后因为每次合并 $ k $ 个字符之后会变成 $ 1 $ 个字符,所以一个区间可以合并成一个字符当且仅当这个区间的长度 $ len \bmod (k-1) = 0 $ 。
我们枚举合并的中间点。为了方便转移,我们设定右边合并成一个字符,左边合并成一个状态,最后再一起合并。
那么这样会不会 WA 飞呢,仔细思考可以发现任意多字符状态的拼接都能拆成“左边保留一个状态、右边每次只贡献一个字符”的逐步拼接,所以固定右边合并成一个字符不会漏掉任何合并顺序。
回到正题,这个时候就要注意了 $ mid $ 不能一个个的枚举,因为如果右边要合并成一个字符的话那么 $ mid $ 到 $ r $ 的长度一定要保证 $ len \bmod (k-1) = 0 $ 。至于怎么做到直接每次增加 $ mid $ 都增加 $ k - 1 $ 就可以了。
我们定义一个 $ len $ 表示左边区间合并后剩余的长度,求这个长度的方法很好理解因为右边的长度模 $ ( k - 1 ) $ 为 $ 0 $ 所以这个长度(也就是左边长度对 $ ( k - 1 ) $ 取模)一定等于整个区间的长度对 $ ( k - 1 ) $ 取模。
然后如果 $ len $ 模 $ ( k - 1 ) $ 为 $ 0 $ ,那么整个区间是可以合并成一个字符,我们就做合并的转移,否则我们就只拼接状态不合并。
转移:
-
对于合并操作,我们枚举左半边合并成的状态,右边则枚举合并成 $ 0 / 1 $ ,然后统计合并方案的最大值。
-
对于拼接操作,我们正常区间dp即可。
最后遍历所有的 $ S $ ,找最大的 $ dp_{n,m,S} $ 即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305,M=1<<7,INF=1e15;
int n,k,a[N],c[1<<8],w[1<<8],dp[N][N][M];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
string s;cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]=='0')a[i+1]=0;
else a[i+1]=1;
}
for(int i=0;i<(1<<k);i++)cin>>c[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int msk=0;msk<M;msk++){
dp[i][j][msk]=-INF;
}
}
}
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
if(i==j){
dp[i][j][a[i]]=0;
continue;
}
int len=(j-i)%(k-1);
if(len==0)len=k-1;
if(len==k-1){
int g[2]={-INF,-INF};
for(int mid=j;mid>i;mid-=k-1){
for(int msk=0;msk<(1<<len);msk++){
int S0=msk<<1,S1=msk<<1|1;
g[c[S0]]=max(g[c[S0]],dp[i][mid-1][msk]+dp[mid][j][0]+w[S0]);
g[c[S1]]=max(g[c[S1]],dp[i][mid-1][msk]+dp[mid][j][1]+w[S1]);
}
}
dp[i][j][0]=g[0];
dp[i][j][1]=g[1];
} else {
for(int mid=j;mid>i;mid-=k-1){
for(int msk=0;msk<(1<<len);msk++){
dp[i][j][msk<<1]=max(dp[i][j][msk<<1],dp[i][mid-1][msk]+dp[mid][j][0]);
dp[i][j][msk<<1|1]=max(dp[i][j][msk<<1|1],dp[i][mid-1][msk]+dp[mid][j][1]);
}
}
}
}
}
int ans=-INF;
for(int msk=0;msk<M;msk++)ans=max(ans,dp[1][n][msk]);
cout<<ans;
return 0;
}

浙公网安备 33010602011771号