Test 2022.09.29
今天是\(SHOI\)专场
T1 智力大冲浪
分析
确实是智力题了,考虑贪心策略:优先安排价值大的,时间能往后就往后,这样前面能选更多东西,如果当前的时间已经有安排了,那么就往前一个位置挪,直到当前时间没有被安排,如果为\(0\),那么久不选当前的东西了。
Code
点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int maxn=600;
struct present{int date,fine;}a[maxn];
bool cmp(present a,present b){return a.fine!=b.fine?a.fine>b.fine:a.date>b.date;}
int n,m;
int vis[maxn];
int all=0;
signed main()
{
scanf("%lld%lld",&m,&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i].date);
for(int i=1;i<=n;i++)scanf("%lld",&a[i].fine),all+=a[i].fine;
sort(a+1,a+n+1,cmp);
int sum=0;
for(int i=1;i<=n;++i)
{
int p=a[i].date;
while(vis[p]==1)p--;
if(p==0){continue;}
vis[p]=1,sum+=a[i].fine;
}
printf("%lld",m-(all-sum));
return 0;
}
/*
10000 7
4 2 4 3 1 4 6
70 60 50 40 30 20 10
10
7
1 2 2 4 2 5 5
0 1 1 1 2 100 1
*/
T2 发微博
分析
本来想着暴力建图,对于每一个加号都暴力遍历然后让\(ans_i++\),然而人傻不会\(vector\)的删除(好像这样可以骗到\(80pts\)),最后只做了一个\(50pts\)的暴力。
突破口
就是题面中的"保证操作合法:即 + x y 时 x 和 y 一定不是好友,而 − x y 时 x 和 y 一定是好友"
再次分析
上一句话是什么意思呢?这是在变相告诉我们:给出的操作一定是若干对"+ -"!!。
可能这个时候还是没反应过来,很正常,看下去就懂了。
很显而易见的是,只有被囊括在这一对对"\(+\quad -\)"里面的"!"操作才会对答案进行贡献,如果暴力遍历每一对所有\(+,-\)里的内容是完全会超时的。
这个时候我们就可以考虑借助后缀和的思想统计,众所周知,"!"操作只有在"+"进行后才能对答案产生贡献,在“-”操作进行后停止对答案产生贡献。
那我们不妨假设"+"之后的"!"操作会一直对答案产生贡献,不受"-"的影响,这样就会多算一部分的贡献值,考虑如何去消除它?很简单,只要减去"-"之后"!"的数量就行了。
这样的话我们就不需要暴力遍历了,只用,倒序遍历,分别统计每个人对应"!"的后缀和\(sum\).
对于每一个"+ x y"的操作,我们把\(ans_x+sum_y,ans_y+sum_x\)
对于每一个"- x y"的操作,我们把\(ans_x-sum_y,ans_y-sum_x\)
接下来应该不用讲了 上代码
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+100;
int ans[maxn],cnt[maxn];
char a[maxn];int x[maxn],y[maxn];
int main()
{
ios::sync_with_stdio(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>a[i]>>x[i];
if(a[i]=='+'||a[i]=='-')cin>>y[i];
}
for(int i=m;i>=1;i--)
{
if(a[i]=='!')cnt[x[i]]++;
else if(a[i]=='-')ans[x[i]]-=cnt[y[i]],ans[y[i]]-=cnt[x[i]];
else if(a[i]=='+')ans[x[i]]+=cnt[y[i]],ans[y[i]]+=cnt[x[i]];
}
for(int i=1;i<n;i++)cout<<ans[i]<<' ';
cout<<ans[n];
return 0;
}
T3 发牌
心路历程
考场上面再次成功想歪了,今天真是一个正解没打出来,以为是在链表上面维护倍增,结果维护就直接\(g\)掉,只有\(20pts\)
分析&正解
对于一次销牌的操作\(R_i\),其实就是把当前序列中前\(R_i\mod len\)个元素按顺序放到序列最后的位置,然后输出当前排在第一位的元素,记上一次跳到的位置是\(pos\)的话,实际上就是在查询这整个序列中第\(((pos+R_i)\mod len)+1\)小的数是谁,输出以后把他剔除掉(或许你可以把这个操作理解成循环队列?)有点难以解释,怎么说呢?从上一个查询的位置开始再跳\(R_i\)个,相当于就是查询现有区间内第\(((pos+R_i)\mod len)+1\)小,然后把\(pos\)迭代成\((pos+R_i)\mod len,len--\),那么就是权值线段树的板子题了。
点击查看代码
#include<bits/stdc++.h>
#define re register
using namespace std;
const int maxn=5e6;
int val[maxn];
inline void read(int &x)
{
x=0;re int f=1;char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=-1;
c=getchar();
}
while('0'<=c&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
x*=f;
}
inline void wr(re int x)
{
if(x<0)putchar('-'),x=~x+1;
if(x>9)wr(x/10);
putchar(x%10^'0');
}
void build(re int x,re int l,re int r)
{
if(l==r){val[x]=1;return ;}
re int mid=(l+r)>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
val[x]=val[x<<1]+val[x<<1|1];
}
int query(re int x,re int l,re int r,re int v)/*查询权值为v的点在哪个位置*/
{
val[x]--;
if(l==r)return l;
re int mid=(l+r)>>1;
if(v<=val[x<<1])return query(x<<1,l,mid,v);
else return query(x<<1|1,mid+1,r,v-val[x<<1]);
}
int main()
{
register int n;
read(n);
build(1,1,n);
register int r,pos=0;
for(register int len=n;len>=1;len--)
{
read(r);
pos=pos+r>=len?(pos+r)%len:pos+r;
wr(query(1,1,n,pos+1)),printf("\n");
}
return 0;
}
T4 阶乘字符串
\(To\quad be\quad continued\)
本文来自博客园,作者:Hanggoash,转载请注明原文链接:https://www.cnblogs.com/Hanggoash/p/16742922.html