7.22 模拟赛
7.22 模拟赛
A 集合
题意:开始集合内只有一个元素 \(0\) ,通过对序列中每一个数进行 \(+1/\text{分裂成和为当前数的两个数}/\text{什么也不干}\) 的三选一事件为一次操作,求达到目标集合的最小操作次数
对于分裂我们不知道什么时候对序列中的哪几个数进行分裂才能达到目标的集合个数,所以我们采用倒推的方法,由结果状态推到初始状态
我们发现集合具有无序性,所以我们可以记录一下每个数在集合中出现的次数,从 \(1\) 开始枚举值域,每次把当前枚举到的值在集合里变成 \(0\) ,并且把已经有的 \(0\) 合起来,为啥要这样做呢,因为分裂以后在集合中操作对答案的贡献更大,你可以同时修改多个点并且只花费一次操作,把操作逆过来就是这样
最后要记得合并没合并完的 \(0\) ,合并操作是上取整,即加一除二
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<queue>
#define N 1000010
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n,a[N],maxn,bac[N],ans=0;
signed main() {
read(n);
for(int i=1;i<=n;i++) read(a[i]),maxn=max(maxn,a[i]),bac[a[i]]++;
for(int i=1;i<=maxn;i++) {
ans++;
bac[0]=bac[i]+(bac[0]+1)/2;
}
while(bac[0]>1) {
ans++;
bac[0]++;
bac[0]/=2;
}
printf("%lld\n",ans);
return 0;
}
B time
题意:给一张有向图,点有点权,一条路径可以重复经过点和边,重复得到点的点权,一条路径的价值为一条路径经过的点权值之和减掉 \(c*\text{经过的边数的平方}\),求最大价值
可以用 \(DP\) 和分层图最长路做
\(DP\) 的话可以设 \(f[i][j]\) 为第 \(i\) 天到第 \(j\) 个点的最大价值,直接枚举天数再枚举每个点进行
的转移即可,\(last\) 可以建反边处理,最后统计 \(f[i][1]-c*i*i\) 的最大值就行了
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#define N 1010
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n,m,c;
int e[4*N],ne[4*N],h[N],idx,val[N];
int f[1010][N],ans;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
signed main(){
memset(h,-1,sizeof h);
read(n),read(m),read(c);
for(int i=1;i<=n;i++) read(val[i]);
for(int i=1;i<=m;i++){
int a,b;
read(a),read(b);
add(b,a);
}
memset(f,-1,sizeof f);
f[0][1]=0;
for(int i=1;i<=1000;i++){
for(int j=1;j<=n;j++){
for(int k=h[j];~k;k=ne[k]){
int op=e[k];
if(f[i-1][op]!=-1) f[i][j]=max(f[i][j],f[i-1][op]+val[j]);
}
}
ans=max(ans,f[i][1]-c*i*i);
}
printf("%lld\n",ans);
return 0;
}
C threesum
题意:维护一个序列\(\{a_i\}\),多组询问,每次询问给定 \(l,r\),求 \([l,r]\) 之间有多少对三元组 \((i,j,k)\) 满足 \(a[i]+a[j]+a[k]=0\)
序列长度小于 \(5000\),显然可以 \(n^2\) 预处理,因为三个变量不好求,所以我们可以先固定两个量,处理第三个量,我们设 \(g[i][j]\) 表示 \([i+1,j-1]\) 之间有多少个数 \(k\) 满足 \(a[i]+a[k]+a[j]=0\) ,这里我们可以开一个区间桶,每次移动区间更新桶,统计合法情况数量,就能处理出来 \(g\),然后就可以用类似二维前缀和的方法来统计剩下两个量变动的情况,设 \(f[i][j]\) 为答案,有
然后发现空间超限,其实我们可以把 \(g\) 数组省掉,在第二次转移中直接用 \(g\) 转移
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#define N 5010
#define ave 1000000
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n,q,a[N],f[N][N],bac[4*ave+10];
signed main(){
read(n),read(q);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
f[i][j]=bac[-a[i]-a[j]+2*ave];
bac[a[j]+2*ave]++;
}
for(int j=i+1;j<=n;j++) bac[a[j]+2*ave]=0;
}
for(int len=3;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
f[i][j]+=f[i+1][j]+f[i][j-1]-f[i+1][j-1];
}
}
while(q--){
int a,b;
read(a),read(b);
printf("%lld\n",f[a][b]);
}
return 0;
}
D boards
题意:给出一个 \(n\times n\) 的坐标轴以及一些点对 \((x1,y1,x2,y2)\),点对表示从点 \((x1,y1)\) 可以无花费地转移到点 \((x2,y2)\),点对满足 \(x1\leq x2,y1\leq y2\),每次移动(只允许向上或向右)一个单位花费为 \(1\),求从 \((0,0)\) 到 \((n,n)\) 的最少花费
可以想到 \(DP\),对于一个点对的 \(x2,y2\),我们可以记录一个 \(f\),表示到该点的最多能节省的花费,这个可以由 \(x\leq x1\; \&\&\; y\leq y1\) 的状态转移过来,我们可以找之前的最大的 \(f\) 来进行转移,于是题目变为二维偏序套 \(DP\) ,二维偏序可以用 \(CDQ\) 解决,于是答案就很好处理了
把一个点对的 \(x1,y1\) 作为查询点,意思是查询横纵坐标都小于等于该点的点的 \(f\) 的最大值,把 \(x2,y2\) 作为修改点,即支持查询的可供修改答案的点,\(CDQ\) 即可
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#define N 200010
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T>
inline void read(T &x){
x=0;bool flag=0;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') flag=1;
for(;isdigit(c);c=getchar()) x=x*10+(c^48);
if(flag) x=-x;
}
int n,cnt=0,p;
int save[N];
int ans[N];
struct node{
int x,y,typ,id;
}op[N];
bool cmp1(node a,node b){
if(a.x==b.x){
if(a.y==b.y){
return a.typ>b.typ;
}else return a.y<b.y;
}else return a.x<b.x;
}
bool cmp2(node a,node b){
if(a.y==b.y){
if(a.x==b.x){
return a.typ>b.typ;
}else return a.x<b.x;
}else return a.y<b.y;
}
void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid);
sort(op+mid+1,op+r+1,cmp2);
int t1=l,t2=mid+1,res=0;
for(;t2<=r;t2++){
if(op[t2].typ) continue;
while(t1<=mid&&op[t1].y<=op[t2].y){
if(op[t1].typ) res=max(res,ans[op[t1].id]);
t1++;
}
ans[op[t2].id]=max(ans[op[t2].id],res+save[op[t2].id]);
}
sort(op+mid+1,op+r+1,cmp1);
CDQ(mid+1,r);
sort(op+l,op+r+1,cmp2);
}
signed main(){
read(n);
read(p);
for(int i=1;i<=p;i++){
int a,b,c,d;
read(a),read(b),read(c),read(d);
save[i]=c-a+d-b;
op[++cnt].x=a,op[cnt].y=b,op[cnt].typ=0,op[cnt].id=i;
op[++cnt].x=c,op[cnt].y=d,op[cnt].typ=1,op[cnt].id=i;
}
op[++cnt].x=0,op[cnt].y=0,op[cnt].typ=1,op[cnt].id=0;
op[++cnt].x=n,op[cnt].y=n,op[cnt].typ=0,op[cnt].id=p+1;
sort(op+1,op+1+cnt,cmp1);
CDQ(1,cnt);
for(int i=1;i<=cnt;i++);
printf("%lld\n",n+n-ans[p+1]);
return 0;
}