7.22考试总结(NOIP模拟23)[联·赛·题]
不拼尽全力去试一下,又怎么会知道啊
前言
又是被细节问题搞掉的一天。
T1 的话,与正解相差无几,少打了两个 else 一个 ls 打成了 rs,然后就爆零了(本来还有 45pts 的),然后加了一个离散化就 A 了(我裂开)
T2 是写了一个贪心,虽然正确性无法保证,但是个别东西打错了,然后在最后几分钟的时候疯狂的改,结果分数越改越少。
答题的时候发现了一个比较好的打法:每次先开最难的题,打出暴力就走人,然后再去搞别的题。
T1 联
解题思路
线段树裸题,实现上注意一下操作的优先级,以及 if 语句后的 else
显然的,1 和 2 操作对于以前的操作是可以直接覆盖的。
3 操作可以将 1 和 2 操作进行对掉。
同时,对于 \(xor\) 的性质而言,两个 3 操作可以相互抵消。
然后就是区间修改以及区间查询了,注意 laz 标记的下放。
虽然,n的范围是 \(10^{18}\) 但是操作数量是非常有限的。
因此可以离散化,对于每一个边界以及边界的前或者后一个数,以及 1 都要进行离散化,毕竟有用的节点不只是边界。。
code
#include<bits/stdc++.h>
#define int long long
#define ls x<<1
#define rs x<<1|1
#define f() cout<<"Pass"<<endl;
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=7e5+110,INF=1e18;
int m,n,tre1[N<<2],tre2[N<<2],laz[N<<2];
int cnt,lsh[N];
struct Ques
{
int opt,l,r;
}q[N];
void push_up(int x)
{
tre1[x]=min(tre1[ls],tre1[rs]);
tre2[x]=min(tre2[ls],tre2[rs]);
}
void build(int x,int l,int r)
{
if(l==r)
{
tre1[x]=l;
tre2[x]=INF;
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
push_up(x);
}
void push_down(int x,int l,int r)
{
if(!laz[x]) return ;
int mid=(l+r)>>1;
if(laz[x]==1)
{
tre1[ls]=tre1[rs]=INF;
tre2[ls]=l;
tre2[rs]=mid+1;
laz[ls]=laz[rs]=1;
laz[x]=0;
return ;
}
if(laz[x]==2)
{
tre2[ls]=tre2[rs]=INF;
tre1[ls]=l;
tre1[rs]=mid+1;
laz[ls]=laz[rs]=2;
laz[x]=0;
return ;
}
if(laz[ls])
{
if(laz[ls]==1)
{
swap(tre1[ls],tre2[ls]);
laz[ls]=2;
}
else if(laz[ls]==2)
{
swap(tre1[ls],tre2[ls]);
laz[ls]=1;
}
else
{
swap(tre1[ls],tre2[ls]);
laz[ls]=0;
}
}
else
{
swap(tre1[ls],tre2[ls]);
laz[ls]=3;
}
if(laz[rs])
{
if(laz[rs]==1)
{
swap(tre1[rs],tre2[rs]);
laz[rs]=2;
}
else if(laz[rs]==2)
{
swap(tre1[rs],tre2[rs]);
laz[rs]=1;
}
else
{
swap(tre1[rs],tre2[rs]);
laz[rs]=0;
}
}
else
{
swap(tre1[rs],tre2[rs]);
laz[rs]=3;
}
laz[x]=0;
}
void update1(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
{
tre1[x]=INF;
tre2[x]=l;
laz[x]=1;
return ;
}
push_down(x,l,r);
int mid=(l+r)>>1;
if(L<=mid) update1(ls,l,mid,L,R);
if(R>mid) update1(rs,mid+1,r,L,R);
push_up(x);
}
void update2(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
{
tre1[x]=l;
tre2[x]=INF;
laz[x]=2;
return ;
}
push_down(x,l,r);
int mid=(l+r)>>1;
if(L<=mid) update2(ls,l,mid,L,R);
if(R>mid) update2(rs,mid+1,r,L,R);
push_up(x);
}
void update3(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
{
if(!laz[x])
{
swap(tre1[x],tre2[x]);
laz[x]=3;
}
else if(laz[x]==1)
{
tre1[x]=l;
tre2[x]=INF;
laz[x]=2;
}
else if(laz[x]==2)
{
tre2[x]=l;
tre1[x]=INF;
laz[x]=1;
}
else
{
swap(tre1[x],tre2[x]);
laz[x]=0;
}
return ;
}
push_down(x,l,r);
int mid=(l+r)>>1;
if(L<=mid) update3(ls,l,mid,L,R);
if(R>mid) update3(rs,mid+1,r,L,R);
push_up(x);
}
void solve()
{
build(1,1,n);
for(int i=1;i<=m;i++)
{
if(q[i].opt==1) update1(1,1,n,q[i].l,q[i].r);
else if(q[i].opt==2) update2(1,1,n,q[i].l,q[i].r);
else if(q[i].opt==3) update3(1,1,n,q[i].l,q[i].r);
printf("%lld\n",lsh[tre1[1]]);
}
}
signed main()
{
m=read();
for(int i=1;i<=m;i++)
{
q[i].opt=read();
q[i].l=read();
q[i].r=read();
lsh[++n]=q[i].l;
lsh[++n]=q[i].r;
lsh[++n]=q[i].l+1;
lsh[++n]=q[i].r+1;
}
lsh[++n]=1;
sort(lsh+1,lsh+n+1);
n=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=m;i++)
{
q[i].l=lower_bound(lsh+1,lsh+n+1,q[i].l)-lsh;
q[i].r=lower_bound(lsh+1,lsh+n+1,q[i].r)-lsh;
}
solve();
return 0;
}
T2 赛
解题思路
首先说明一下贪心做法是错误的,毕竟对于每一种情况我们无法保证选的满足两个人需要的最多就是最优的。
但是可以骗到 70pts
贪心不行,那就暴力枚举呗。
对于所有的物品,我们分为四大类:
-
两个人都喜欢的
-
第一个人喜欢但是第二个人不喜欢的
-
第二个人喜欢但是第一个人不喜欢的
-
其它的
我们可以暴力枚举第一类物品的数量,然后对应修改选的第二三类物品的数量。
然后在第四类物品中选择最小的若干个。
对于最小的前几个数可以用动态开点(好像不开也可以)权值线段树来维护。
代码实现细节比较多。。
当然这个题也可以在暴力的基础上三分法搞。
code
70pts的贪心
#include<bits/stdc++.h>
#define int long long
#define ls x<<1
#define rs x<<1|1
#define f() cout<<"Pass"<<endl;
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=5e5+10;
int n,m,k,ans,pos,s[N];
int cnt1,cnt2,q1[N],q2[N];
int top,sta[N],res[N];
int t1,t2,s1[N],s2[N];
bool vis[N];
vector<int> v;
bool comp(int x,int y)
{
return s[x]<s[y];
}
signed main()
{
n=read();
m=read();
k=read();
for(int i=1;i<=n;i++)
s[i]=read();
cnt1=read();
for(int i=1;i<=cnt1;i++)
{
q1[i]=read();
vis[q1[i]]=true;
}
cnt2=read();
for(int i=1;i<=cnt2;i++)
{
q2[i]=read();
if(vis[q2[i]]) v.push_back(q2[i]);
else vis[q2[i]]=true;
}
if(k>cnt1||k>cnt2)
{
printf("-1");
return 0;
}
memset(vis,false,sizeof(vis));
for(int i=0;i<v.size();i++)
sta[++top]=v[i];
vector<int>().swap(v);
sort(sta+1,sta+top+1,comp);
for(int i=1;i<=top;i++)
vis[sta[i]]=true;
for(int i=1;i<=cnt1;i++)
if(!vis[q1[i]])
s1[++t1]=q1[i];
for(int i=1;i<=cnt2;i++)
if(!vis[q2[i]])
s2[++t2]=q2[i];
memset(vis,false,sizeof(vis));
sort(s1+1,s1+t1+1,comp);
sort(s2+1,s2+t2+1,comp);
for(pos=1;pos<=2*k-m;pos++)
ans+=s[sta[pos]],vis[sta[pos]]=true;
int pre=pos;
int temp=k-pos+1;
int pos1=1,pos2=1;
if(2*temp>m||pos>top+1)
{
cout<<-1;
return 0;
}
for(int i=1;i<=temp;i++)
{
if(pos==top+1&&pos1==cnt1+1)
{
cout<<-1;
return 0;
}
if(pos==top+1)
{
vis[s1[pos1]]=true;
ans+=s[s1[pos1]];
pos1++;
continue;
}
if(s[sta[pos]]<=s[s1[pos1]])
{
vis[sta[pos]]=true;
ans+=s[sta[pos]];
pos++;
continue;
}
vis[s1[pos1]]=true;
ans+=s[s1[pos1]];
pos1++;
}
for(int i=pos-pre+1;i<=temp;i++)
{
if(pos==top+1&&pos2==cnt2+1)
{
cout<<-1;
return 0;
}
if(pos==top+1)
{
vis[s2[pos2]]=true;
ans+=s[s2[pos2]];
pos2++;
continue;
}
if(s[sta[pos]]<=s[s2[pos2]])
{
vis[sta[pos]]=true;
ans+=s[sta[pos]];
pos++;
continue;
}
vis[s2[pos2]]=true;
ans+=s[s2[pos2]];
pos2++;
}
int cnt=0;
for(int i=1;i<=n;i++)
if(!vis[i])
res[++cnt]=i;
sort(res+1,res+cnt+1,comp);
if(cnt<m-pos1-pos2-pos+3)
{
cout<<-1;
return 0;
}
for(int i=1;i<=m-pos1-pos2-pos+3;i++)
ans+=s[res[i]];
printf("%lld",ans);
return 0;
}
正解
#include<bits/stdc++.h>
#define int long long
#define ls tre[x].l
#define rs tre[x].r
#define f() cout<<"Pass"<<endl;
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=5e5+10,INF=1e18;
int n,m,k,ans,pos,tot,root,s[N];
int cnt1,cnt2,q1[N],q2[N];
int top,cnt,lsh[N],sta[N];
int t1,t2,t3,s3[N],s1[N],s2[N];
bool vis[N];
vector<int> v;
bool comp(int x,int y)
{
return s[x]<s[y];
}
struct VOP_Segment_Tree
{
int l,r,siz,dat;
}tre[N*80];
void push_up(int x)
{
tre[x].dat=tre[ls].dat+tre[rs].dat;
tre[x].siz=tre[ls].siz+tre[rs].siz;
}
void insert(int &x,int l,int r,int pos,int val)
{
if(!x) x=++tot;
if(l==r)
{
tre[x].dat+=val*lsh[l];
tre[x].siz+=val;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) insert(ls,l,mid,pos,val);
else insert(rs,mid+1,r,pos,val);
push_up(x);
}
int query(int x,int l,int r,int rk)
{
if(rk<=0) return 0;
if(l==r) return tre[x].dat;
int mid=(l+r)>>1;
if(rk<=tre[ls].siz) return query(ls,l,mid,rk);
return tre[ls].dat+query(rs,mid+1,r,rk-tre[ls].siz);
}
signed main()
{
n=read();
m=read();
k=read();
for(int i=1;i<=n;i++)
lsh[i]=s[i]=read();
cnt1=read();
for(int i=1;i<=cnt1;i++)
{
q1[i]=read();
vis[q1[i]]=true;
}
cnt2=read();
for(int i=1;i<=cnt2;i++)
{
q2[i]=read();
if(vis[q2[i]]) sta[++top]=q2[i];
else vis[q2[i]]=true;
}
if(cnt1<k||cnt2<k||top<2*k-m)
{
cout<<-1;
return 0;
}
sort(sta+1,sta+top+1,comp);
sort(lsh+1,lsh+n+1);
cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)
s[i]=lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh;
memset(vis,false,sizeof(vis));
top=min(top,m);
for(int i=1;i<=top;i++)
vis[sta[i]]=true;
for(int i=1;i<=cnt1;i++)
if(!vis[q1[i]]) s1[++t1]=q1[i],vis[q1[i]]=true;
for(int i=1;i<=cnt2;i++)
if(!vis[q2[i]]) s2[++t2]=q2[i],vis[q2[i]]=true;
sort(s1+1,s1+t1+1,comp);
sort(s2+1,s2+t2+1,comp);
for(int i=1;i<=top;i++)
ans+=lsh[s[sta[i]]];
for(int i=1;i<=k-top;i++)
ans+=lsh[s[s1[i]]]+lsh[s[s2[i]]];
for(int i=k-top+1;i<=t1;i++)
vis[s1[i]]=false;
for(int i=k-top+1;i<=t2;i++)
vis[s2[i]]=false;
for(int i=1;i<=n;i++)
if(!vis[i])
insert(root,1,cnt,s[i],1);
int sum=ans;
ans+=query(1,1,cnt,m-2*k+top);
for(int i=top-1;i>=0;i--)
{
if(i<2*k-m) break;
if(k-i>cnt1||k-i>cnt2) break;
sum-=lsh[s[sta[i+1]]];
insert(root,1,cnt,s[sta[i+1]],1);
if(k-i<1) continue;
sum+=lsh[s[s1[k-i]]]+lsh[s[s2[k-i]]];
insert(root,1,cnt,s[s1[k-i]],-1);
insert(root,1,cnt,s[s2[k-i]],-1);
ans=min(ans,sum+query(1,1,cnt,m-2*k+i));
}
printf("%lld",ans);
return 0;
}
T3 题
解题思路
非常妙的一个题。。。
首先,每个人挑选的顺序不同,答案是不同的。
正着枚举不是特别好判断两者是否能共存,因此我们选择逆推。
对于每两个不能同时存在的状态,是可以递推到这两个节点和其它节点的。
类似于路径压缩一类的东西。
对于三者之间的共存关系,因为是逆推,如果当前扫到的“边”的两个连接点
如果两个点与现在扫到的节点都不可以共存,那么显然这个节点是无法存活到最后的。
如果当前连边的两个节点中有一个节点是无法与当前扫到节点共存的话,那么另一个节点也不可以。
最后对于答案统计的时候需要用到集合之间的交集,可以用 bitset 维护。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=410,M=5e4+10;
int n,m,ans;
bitset<N> f,g[N];
struct Node
{
int x,y;
void insert()
{
x=read();
y=read();
}
}s[M];
signed main()
{
n=read();
m=read();
for(int i=1;i<=m;i++)
s[i].insert();
for(int i=1;i<=n;i++)
{
g[i][i]=true;
for(int j=m;j>=1;j--)
{
if(g[i][s[j].x]&&g[i][s[j].y])
{
f[i]=true;
break;
}
if(g[i][s[j].x]||g[i][s[j].y])
g[i][s[j].x]=g[i][s[j].y]=true;
}
}
for(int i=1;i<=n;i++)
if(!f[i])
for(int j=1;j<i;j++)
if(!f[j]&&!(g[i]&g[j]).count())
ans++;
printf("%lld",ans);
return 0;
}