8.19题解
T1
考场上看见之后就觉得似曾相识,等比变等差,简单了啊,打完之后更觉得自己稳了,结果死在了上次那道题卡我的地方,就是关于数列中的去重
我用的set去重,当然map也可以,然后就从头到尾扫一遍,如果碰到重复的,或者当前两个数之间的差和之前的公比的$gcd$为1,就清空set,给ans++,扫到最后就可以了
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<set> 5 #define maxn 100100 6 using namespace std; 7 int n,d,ans; 8 int a[maxn]; 9 set <int> s; 10 int gcd(int a,int b) 11 { 12 return (b==0)?a:gcd(b,a%b); 13 } 14 int main() 15 { 16 scanf("%d",&n); 17 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 18 int pos=1; s.insert(a[1]); 19 while(pos<=n) 20 { 21 while(gcd(d,abs(a[pos+1]-a[pos]))!=1&&!s.count(a[pos+1])&&pos<n) 22 { 23 d=gcd(d,abs(a[pos+1]-a[pos])); 24 pos++; s.insert(a[pos]); 25 } 26 pos++; ans++; d=0; s.clear(); s.insert(a[pos]); 27 } 28 printf("%d\n",ans); 29 return 0; 30 }
T2
考试的时候选择了$bieset$?我zz,不用理我,正解说是离线,但是我觉得离线不太对的样子,就直接在线了,对于每次的交并操作,建立父子关系,都是有向边,被完全包含的那个当儿子,查询的时候就dfs看y的儿子中有没有x就可以了,特殊一点的是$k=1$的情况,这个时候交并都是一个效果,所以需要双向边,所以在$dfs$的时候注意判断一下有没有被走过,不然会死,还有就是由于数组稍大,$memset$会死,所以就一边用一边清,或者用$bitset$打标记就可以了
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define maxn 500100 5 using namespace std; 6 int n,m,js,bj,opt,op,k; 7 int head[maxn],to[maxn],xia[maxn],pd[maxn]; 8 void add(int x,int y) 9 { 10 to[++js]=y; xia[js]=head[x]; head[x]=js; 11 } 12 void dfs(int x,int qu) 13 { 14 if(bj==1) return ; 15 if(x==qu) {bj=1; return ;} 16 pd[x]=1; 17 for(int i=head[x];i;i=xia[i]) 18 { 19 int ls=to[i]; 20 if(!pd[ls]&&bj!=1) dfs(ls,qu); 21 } 22 pd[x]=0; 23 } 24 int main() 25 { 26 scanf("%d%d",&n,&m); 27 while(m--) 28 { 29 scanf("%d",&opt); 30 if(!opt) 31 { 32 ++n; scanf("%d%d",&op,&k); 33 if(k==1) {int i; scanf("%d",&i); add(n,i); add(i,n);} 34 else 35 { 36 if(!op) 37 for(int i=1;i<=k;++i) {int x; scanf("%d",&x); add(x,n);} 38 else 39 for(int i=1;i<=k;++i) {int x; scanf("%d",&x); add(n,x);} 40 } 41 } 42 else 43 { 44 int x,y; scanf("%d%d",&x,&y); bj=0; 45 dfs(y,x);//看x是不是y的儿子 46 printf("%d\n",bj); 47 } 48 } 49 return 0; 50 }
T3
这题的dp我在考场上想出来了,并且得到了50分的好成绩,当时满脑子都是什么树状数组,线段树什么的优化dp,没想到用堆
首先最简单的dp式子,假设$dp[i]$代表在$i$下车的最小花费,$dp[i]=min(dp[i],dp[j]+max(qz[i]-qz[j]),b[j])$,$qz$是对$a$数组求了前缀和,这样的话复杂度是$O(nk)$的,显然过不去,我们来考虑优化,也就是优化$min(dp[j]+max(qz[i]-qz[j],b[j]))$,可以发现$dp[j]+b[j]$是定值,$dp[j]-qz[j]+qz[i]$单调增的,所以我们可以开两个小根堆,一个维护$dp[j]+b[j]$,一个维护$dp[j]-qz[j]$,这样的话就可以愉快的取$min$了,那么那个$max$的要求怎么办呢?我们发现$dp[j]-qz[j]+qz[i]$单增,$dp[j]+b[j]$不变,那在第一次$dp[j]+b[j]<dp[j]-qz[j]+qz[i]$成立的时候这之后都只会用$dp[j]-qz[j]$,所以在每次取堆顶的时候都判断一下是否满足那个式子,如果满足就从第一个堆踢出来,扔进第二个堆,在每次取用的时候,判一下下标也就是距离是否满足即可
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<queue> 5 #define maxn 500100 6 #define int long long 7 using namespace std; 8 struct node{ 9 int sum,pos; 10 }; 11 bool operator < (const node &a,const node &b) 12 { 13 return a.sum>b.sum; 14 } 15 int n,k; 16 int qz[maxn],b[maxn],dp[maxn]; 17 priority_queue <node> dz;//dp+b 18 priority_queue <node> ddz;//dp-qz 19 signed main() 20 { 21 scanf("%lld%lld",&n,&k); 22 for(int i=1;i<=n;++i) {scanf("%lld",&qz[i]); qz[i]+=qz[i-1];} 23 for(int i=0;i<n;++i) scanf("%lld",&b[i]); 24 dz.push((node){b[0],0}); 25 dz.push((node){5000000000000000000,n+1}); ddz.push((node){5000000000000000000,n+1}); 26 for(int i=1;i<=n;++i) 27 { 28 while(dz.size()&&dz.top().pos<max(1ll*0,i-k)) dz.pop(); 29 while(ddz.size()&&ddz.top().pos<max(1ll*0,i-k)) ddz.pop(); 30 while(dz.size()&&dz.top().sum<dp[dz.top().pos]-qz[dz.top().pos]+qz[i]) 31 { 32 ddz.push((node){dp[dz.top().pos]-qz[dz.top().pos],dz.top().pos}); 33 dz.pop(); 34 } 35 while(dz.size()&&dz.top().pos<max(1ll*0,i-k)) dz.pop(); 36 while(ddz.size()&&ddz.top().pos<max(1ll*0,i-k)) ddz.pop(); 37 dp[i]=min(dz.top().sum,ddz.top().sum+qz[i]); 38 dz.push((node){dp[i]+b[i],i}); 39 } 40 printf("%lld\n",dp[n]); 41 return 0; 42 }
最开始在两个堆中插入极大值是为了防止堆被pop空,查出0来