雅禮學習10.5
上午考試
各題狀況
T1
模擬掛成\(10\)分??
每次更新答案的時候位置搞錯了。
想到了可能是線段樹動態開點,但沒寫出來,因爲標記下傳不會。。。
T2
理解錯了題目含義。
選出的\(m\)個物品中,至少要有\(k\)個是\(A\)喜歡的,至少\(k\)個是\(B\)喜歡的
那麼很顯然只要滿足了上面的限制條件,倆人都不喜歡的也能選。。。
但考場上沒想到這層
就涼了
正解變騙分,\(15\)分
T3
搞完上面兩個題目之後沒剩多少時間,就隨便扔了個東西上去。。
也不知道寫的是個啥
題目及考場代碼
T1
/*
* 第一眼感覺是線段樹
* 想了想,感覺線段樹太浪費了,bitset應該可以搞
* 但是。。。看了數據之後
* 長度爲10^18的01序列
* 這。。做個頭啊做。
*
* 第三種操作是0變1,1變0
*
* 對於n,m<=1000的數據,可以模擬
* 對於只有1操作的情況,考慮維護全0區間和全1區間的分界點
* 對於只有1,2操作的情況,因爲是強制變成0或1,所以同樣只考慮分界點
* 對於n<=10^5的情況,線段樹應該可以?但是不會維護。
*/
#include <cstdio>
#include <algorithm>
inline long long read()
{
long long n=0;int w=1;register char c=getchar();
while(c<'0' || c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
const int M=1e5+1,qwq[]={0,0,1};
int a[100001],ty[M];
long long l[M],r[M];
std::pair<int,int> pr[M];
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
int m,flag=0;long long n=0;
m=read();
for(int i=1;i<=m;++i)
{
ty[i]=read(),l[i]=read(),r[i]=read();
n=std::max(n,r[i]);
flag=std::max(flag,ty[i]);
}
if(flag==1)
{
int id=1,cnt=0;
for(int i=1;i<=m;++i)
{
if(l[i]<=id && r[i]>=id)
{
int x=r[i]+1;
for(int j=1;j<i;++j)
if(l[j]<=x && r[j]>=x)
x=r[j]+1;
id=x;
}
printf("%d\n",id);
}
}
else
if(n<=100000)
{
int id=1;
for(int x,i=1;i<=m;++i)
{
if(ty[i]!=3)
{
x=(ty[i]==1?1:0);
for(int j=l[i];j<=r[i];++j)
a[j]=x;
}
else
for(int j=l[i];j<=r[i];++j)
a[j]^=1;
if(l[i]<=id && id<=r[i])
{
for(;l[i]<=r[i];++l[i])
if(!a[l[i]])
{
id=l[i];
break;
}
}
printf("%d\n",id);
}
}
else
while(m--)
puts("1");
fclose(stdin),fclose(stdout);
return 0;
}
T2
/*
* 分成兩人都喜歡的,只有A喜歡的,只有B喜歡的
* 然後枚舉從兩個人都喜歡的物品裏面選k個最便宜的
* 然後貪心選剩下的部分
*
* 大樣例跑不出來。。
*/
#include <cstdio>
#include <algorithm>
inline int read()
{
int n=0,w=1;register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
const int N=2e5+1;
int n,m,k,a[N],b[N],v[N],both[N];
/*
int aaa,bbb;
void dfs(int now,int step,int aa,int bb)
{
if(aa>k || bb>k)return ;
if(step==m)
{
if(aa==k && bb==k)
++ans;
return ;
}
if(aaa==now)
{
++aaa;
if(bbb==now)
{
++bbb;
dfs(now+1,step+1,aa+1,bb+1);
dfs(now+1,step,aa,bb);
--bbb;
}
else
{
dfs(now+1,step+1,aa+1,bb);
dfs(now+1,step,aa,bb);
}
--aaa;
}
else
if(bb==now)
{
++bbb;
dfs(now+1,step+1,aa,bb+1);
dfs(now+1,step,aa,bb);
--bbb;
}
}
*/
inline bool cmp(const int &x,const int &y)
{return v[x]<v[y];}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
n=read(),m=read(),k=read();
for(int i=1;i<=n;++i)
v[i]=read();
int aa=read(),bb;
for(int i=1;i<=aa;++i)
a[i]=read();
bb=read();
for(int i=1;i<=bb;++i)
b[i]=read();
std::sort(a+1,a+1+aa);
std::sort(b+1,b+1+bb);
//哪些倆人都喜歡
int aaa=1,bbb=1,emp=0;
while(aaa<=aa && bbb<=bb)
{
if(a[aaa]>b[bbb])
++bbb;
else
if(a[aaa]<b[bbb])
++aaa;
else
{
both[++emp]=a[aaa];
++aaa,++bbb;
}
}
/*
for(int i=1;i<=emp;++i)
printf("%d ",both[i]);
*/
//
std::sort(both+1,both+1+emp,cmp);
long long ans=0;
for(int i=1;i<=k;++i)
ans+=v[both[i]];
if(emp>k)
{//先把都喜歡的減掉
m-=k;
k=0;
}
else
{
m-=emp;
k-=emp;
}
//判斷剩下的是不是夠
if(m<(k<<1))
{
printf("-1");
fclose(stdin);fclose(stdout);
return 0;
}
int cnt=1;emp=0;
for(int i=1;i<=aa;++i)
{
if(a[i]!=both[cnt])
a[++emp]=a[i];
else ++cnt;
}
aa=emp;
cnt=1,emp=0;
for(int i=1;i<=bb;++i)
{
if(b[i]!=both[cnt])
b[++emp]=b[i];
else ++cnt;
}
bb=emp;
if(k>aa || k>bb)
{
printf("-1");
fclose(stdin);fclose(stdout);
return 0;
}
/*
for(int i=1;i<=aa;++i)
printf("%d ",a[i]);
puts("");
for(int i=1;i<=bb;++i)
printf("%d ",b[i]);
*/
std::sort(a+1,a+1+aa,cmp);
std::sort(b+1,b+1+bb,cmp);
//然後貪心選剩下的
for(int i=1;i<=k;++i)
ans+=v[a[i]]+v[b[i]];
int i=k+1,j=k+1;
while(i+j<m)
{
if(v[a[i]]>v[b[j]])
ans+=v[b[j++]];
else ans+=v[a[i++]];
}
printf("%lld",ans);
fclose(stdin);fclose(stdout);
return 0;
}
T3
#include<cstdio>
#include<iostream>
#include<fstream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define Set(a) memset(a,0,sizeof(a))
#define F(i,a,b) for(int i=a;i<=b;++i)
#define UF(i,a,b) for(int i=a;i>=b;--i)
#define openf(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
typedef long long LL;
typedef long long ll;
typedef unsigned long long ULL;
typedef unsigned long long ull;
const int maxn=400+10;
const int maxm=5e4+10;
ll n,m;
ll u[maxm],v[maxm];
bool used[maxn],a[maxn][maxn];
ll f[maxn],xx;
ll tt;
void check()
{
xx=0;
Set(f);
F(i,1,n) if(!used[i]) f[++xx]=i;
F(i,1,xx)
F(j,1,i)
if(f[i]!=f[j]&&!a[f[i]][f[j]]) {a[f[i]][f[j]]=1;++tt;}
}
void dfs(int step)
{
if(step==m+1) check();
ll u1=u[step],v1=v[step];
if(!used[u1]&&!used[v1]){used[u1]=1;dfs(step+1);used[u1]=0;used[v1]=1;dfs(step+1);used[v1]=0;}
if( used[u1]&&!used[v1]){used[v1]=1;dfs(step+1);used[v1]=0;}
if(!used[u1]&& used[v1]){used[u1]=1;dfs(step+1);used[u1]=0;}
if( used[u1]&& used[v1])dfs(step+1);
return;
}
int main()
{
openf(c);
scanf("%d%d",&n,&m);
F(i,1,m) scanf("%d%d",&u[i],&v[i]);
Set(used);
Set(a);
dfs(1);
printf("%lld",tt);
return 0;
}
正解及代碼
T1
離散化線段樹
對每個區間,維護一個\(tag\),維護的是最左邊的\(0\)和最左邊的\(1\)的位置
那麼\(1\)操作就是把這個區間的\(tag\)變成區間左端點
然後考慮標記下傳
\(1\)操作\(+\)任意操作還是\(1\)操作
\(2\)操作\(+\)任意操作還是\(2\)操作
\(3\)操作\(+1\)操作變成\(2\)操作
\(3\)操作\(+2\)操作變成\(1\)操作
\(3\)操作\(+3\)操作變成沒有操作
然後直接離散化+線段樹就行了,不需要動態開點
T2
把所有的物品分成:兩人都喜歡的,\(A\)喜歡的,\(B\)喜歡的,都不喜歡的
枚舉兩人都喜歡的物品選了多少個(假設是\(x\)),那麼再在\(A\)喜歡的物品中和\(B\)喜歡的點鐘至少都還要選\(k-x\)個(用貪心的思路想,這裏顯然要選權值前\(k-x\)小的那些),然後如果還是沒有選夠\(m\)個,就在兩人都不喜歡的物品中選最小的那些直到滿足條件
那麼從小到大枚舉\(x\),不停求和就行了
T3
對這個問題,考慮一個更一般的問題:假設\(f_k(S)=f_{k-1}(S\cup \{v_i\})\)表示\(k\)個人來過之後,\(S\)集合內的蘋果都還存活的概率是否\(\gt 0\),討論得到三種情況
- \(u_k,v_k\)都在\(S\)中,那麼\(f_k(S)=0\)(兩者至少要吃掉一個,所以\(S\)集合內的蘋果不可能都剩下)
- \(u_k,v_k\)有一個在\(S\)中,假設是\(u_k\)在\(S\)中,那麼\(f_k(S)=f_{k-1}(S\cup \{v_i\})\)
- \(u_k,v_k\)都不在\(S\)中,那麼\(f_k(S)=f_{k-1}(S)\)
那麼現在將所有的\(f_m(\{p\})=1\)都求出來,順便求出\(g(p)\)是從\(f_m(\{p\})\)運行上面算法得到的最終集合
觀察得到\((u,v)\)合法的條件就是\(f_m(\{u\})=1,f_m(\{v\})=1,g(u)\cap g(v)=\varnothing\)
直接枚舉,即可得到答案
下午講課:樹上的題
例1
給定一棵\(n\)個節點的樹,你可以進行\(n−1\)次操作,每次操作步驟如下:
- 選擇\(u,v\)兩個度數爲\(1\)的節點。
- 將\(u,v\)之間的距離加到\(ans\)上。
- 將\(u\)從樹上刪除。
求一個操作序列使得\(ans\)最大。
解:
先把直徑拿出來,將直徑外的點一個一個的和直徑中的某一個端點配對並刪掉,最後再將直徑刪掉。
證明:
如果當前直徑外已經沒有點了,那麼顯然只能將直徑的端點刪掉;否則一定不會去刪直徑的端點。
因爲先刪一個直徑外的點再刪直徑端點一定是不劣於先刪直徑端點再刪這個直徑外的點的。
比如有這樣三個點\(x,y,z\),\(x,z\)是該樹直徑的兩端,
假設刪除順序是\(x,y\),那麼\(ans+=dis[x][z]+dis[y][z]\)
假設刪除順序是\(y,x\),那麼\(ans+=\max(dis[x][y],dis[y][z])+dis[x][z]\)
例2
給定一張\(n\)個點\(m\)條邊的帶權無向聯通圖,\(q\)次詢問,每次詢問\(u_i\)到\(v_i\)的最短路長度
\(n,q\le 10^5,m-n\le 20\)
解:
注意到\(m-n\le 20\),那麼這張圖其實就是一個樹多了屈指可數的幾條非樹邊
先隨便搞一棵生成樹,那麼會有幾條邊不在這個生成樹上,標記那些有非樹邊連接的點
那麼被標記的點最多\(40\)個,先跑\(40\)遍單源最短路求出這些被標記的點到其餘所有點的最短路
對於一個詢問,如果最短路(跑\(LCA\)來求)只經過生成樹上的邊,那麼直接計算;若經過了被標記的點,就查看走這個點連接的非樹邊是否會對答案造成影響,嘗試更新就行了
例3
給定一棵\(n\)個節點的樹,定義葉子是度數爲\(1\)的節點,點集\(S\)是好的如果滿足任意兩個\(S\)中的點之間的距離\(\le k\),現在要求將所有葉子分成若干不相交的好的集合,請問最少分成多少個好的集合。
\(n,k\le 10^6\)
解:
講師說這類題目一般都可以貪心,我也不知道是哪類題目。。。(話說樹上題目有什麼分類的麼orz)
先隨便選一個根,方便起見選一個不是葉子的根,然後可以自底向上貪心。
設\(f_p\)表示\(p\)爲根的子樹內已完成的好的集合的數量,\(g_p\)表示未完成的集合中離\(p\)最遠的葉子的距離。
考慮如何計算\(f_p\)和\(g_p\)。
首先將所有兒子的\(f\)先求和,然後將所有兒子的\(g\)排序,檢查 一下最大的\(g\)和次大的\(g\)相加是否大於\(k\),如果大於,那麼就將最大的\(g\)刪掉,並把\(f_p\)加上\(1\)。否則就令\(g_p\)等於最大的\(g+1\)。
例4
給定\(n\)個節點,每個節點上有一個數字,要求用這些節點構建一棵二叉搜索樹,滿足有邊相鄰的兩個節點上的數字互質
\(n\le 600\)
解:
先排序,令\(f[i][j][k]\)表示第\(i\)個點到第\(j\)個點以\(k\)爲根是否可行
轉移的時候首先檢查是否存在\(u\in [i,k-1]\)滿足\(f[i][k-1][u]=1\)且\(u\)上的數字跟\(k\)上的數字互質
右側同理,檢查是否存在\(v\in [k+1,j]\)滿足\(f[k+1][j][v]=1\)且\(v\)上的數字跟\(k\)上的數字互質
但是這樣是\(O(n^4)\)的(數組是\(n^3\)的,轉移是\(n\)的)
考慮優化:
注意到上面的做法有一些重複運算。於是設\(L[i][j]\)表示是否存在\(k\in [i,j]\)滿足\(f[i][j][k]=1\)且\(k\)上的數字和\(j+1\)上的數字互質,類似地定義\(R[i][j]\)