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 }
View Code

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 }
View Code

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两遍留给大家思考,可以手玩样例,或者性感理解

最开始在两个堆中插入极大值是为了防止堆被pop空,查出0来

posted @ 2019-08-19 19:49  hzoi_X&R  阅读(176)  评论(0编辑  收藏  举报