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;
}