题目链接:https://codeforces.com/gym/103470/problem/A
题意:给定一个n*n的方格,每个方格中可能会有袋鼠,你需要操作所有的袋鼠使得最后所有的袋鼠都能够到达(a,b),给出一种操作次数不超过3*(n-1)的操作方案。每次操作我们都会使得所有的袋鼠向一个方向移动,不能移动的袋鼠就不移动。ULDR为操作的命令
分析:我们首先需要将所有的袋鼠移动到同一个位置,然后再统一移动所有的袋鼠,我们可以将所有的袋鼠都移动到四个角落的某一个角落,具体移动到哪个角落取决于(a,b)距离哪个角落更近。
- #include<iostream>
- #include<algorithm>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #include<vector>
- #include<queue>
- using namespace std;
- const int N=2e5+10;
- int main()
- {
- int n,a,b;
- cin>>n>>a>>b;
- if(a<=n/2)//上半面
- {
- if(b<=n/2)//左上
- {
- for(int i=1;i<n;i++)
- printf("LU");
- for(int i=1;i<a;i++)
- printf("D");
- for(int i=1;i<b;i++)
- printf("R");
- }
- else//右上
- {
- for(int i=1;i<n;i++)
- printf("RU");
- for(int i=1;i<a;i++)
- printf("D");
- for(int i=1;i<=n-b;i++)
- printf("L");
- }
- }
- else//下半面
- {
- if(b<=n/2)//左下
- {
- for(int i=1;i<n;i++)
- printf("LD");
- for(int i=1;i<=n-a;i++)
- printf("U");
- for(int i=1;i<b;i++)
- printf("R");
- }
- else//右下
- {
- for(int i=1;i<n;i++)
- printf("RD");
- for(int i=1;i<=n-a;i++)
- printf("U");
- for(int i=1;i<=n-b;i++)
- printf("L");
- }
- }
- return 0;
- }

样例输入:
- 9 -100
- -1 -2 1 2 -1 -2 1 -2 1
样例输出:
3
题意:给你一个长度为n的数组和一个整数k,我们可以对数组进行操作,但是操作次数不得超过1,操作就是选择一个区间[l,r],把位于这个区间内的所有数都加上k,现在问我们操作后数组中出现次数最大的数的出现次数的最大值。
分析:对于每一个a[i]我们求一下他对a[i]+k产生的最大有效贡献即可。什么叫最大有效贡献呢?假如我们选定的区间中有p个a[i],q个a[i]+k,那么有效贡献就是p-q,这是比较容易理解的,因为操作后会多产生p-q个a[i]+k,所以我们只要遍历一下每一个a[i]即可,最后过程中记录一下最大值即可。关键是怎么求最大有效贡献呢?我们首先记录一下a[i]的出现位置并存入一个数组中,同理记录一下a[i]+k的出现位置并存入另一个数组中,对这两个数组进行从小到大排序,并设置两个指针分别指向两个数组的起始位置,然后每次选择一个靠前的位置,如果是a[i]靠前,那么我们就令贡献+1,否则令贡献-1,并移动相应的指针,如果贡献一旦减为负数,那么就令贡献为0,这个思想就是类似于最大子段和,最后在过程中记录一下每个a[i]的最大有效贡献即可。
需要特别注意的一点就是k=0的情况,这个时候直接取出现次数最大的数的出现次数作为答案即可。
- #include<iostream>
- #include<algorithm>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #include<vector>
- #include<queue>
- using namespace std;
- const int N=4e6+10;
- vector<int>p[N];
- vector<int>alls;
- int main()
- {
- int n,k;
- cin>>n>>k;
- int t;
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&t);
- p[t+2000001].push_back(i);
- alls.push_back(t+2000001);
- }
- sort(alls.begin(),alls.end());
- alls.erase(unique(alls.begin(),alls.end()),alls.end());
- int ans=0;
- for(int i=0;i<alls.size();i++)
- {
- ans=max(ans,(int)p[alls[i]].size());
- if(k==0) continue;
- if(p[alls[i]+k].size()==0) continue;
- int f=0;//计算最多可以将多少个alls[i]变为alls[i]+k
- int nowf=0;//计算当前贡献
- int l1=0,l2=0;
- int len1=p[alls[i]].size(),len2=p[alls[i]+k].size();
- while(l1<len1&&l2<len2)
- {
- if(p[alls[i]][l1]<p[alls[i]+k][l2])
- {
- nowf++;
- f=max(f,nowf);
- l1++;
- }
- else
- {
- nowf--;
- if(nowf<0) nowf=0;
- l2++;
- }
- }
- if(l1!=len1)
- {
- nowf+=len1-l1;
- f=max(f,nowf);
- }
- ans=max(ans,f+len2);
- }
- printf("%d",ans);
- return 0;
- }
题目链接:https://codeforces.com/gym/103470/problem/D

样例输入:
- 3
- 5
- 2 3 2 1 5
- 3
- 1 2 3
- 1
- 1
样例输出:
- 0 2 3 5 7
- 0 2 4
- 0
题意:给定一个长度为n的数组,然后给定一个排序代码,我们需要利用上述代码对原数组进行排序,问交换次数,输出是针对前k个元素排序的交换次数,输出k=1,2,……,n情况下的交换次数。
分析:先考虑对前k个元素进行排序的结果,对于i从1~k遍历,第i次遍历完的数组一定有前i个元素有序,那么我们下一次就是对第i+1个元素进行遍历交换,比如前i次排序后的结果是
2 5 8 8 9 6,其中i等于4,下一次对第5个位置进行排序,那么我们会先用6和8交换,然后再用8和9交换,得到2 5 6 8 8 9,交换次数是2,交换次数就是大于6的不重复的连续的元素个数,这个刚好可以用树状数组进行维护,所以对于固定前k个元素的情况我们可以O(klogk)解决,关键是我们怎么由k的答案得到k+1时的答案,因为我们加入第k+1个数时,这个时候我们需要对第k+1个数进行讨论,如果第k+1个数小于等于前k个数的最大值,那么我们可以知道在前k轮排序过程中是不会涉及到第k+1个数的,因为最大值之后的数是不可能发生交换的,那么只有在第k+1轮排序时才会对第k+1个数进行交换,那么他会交换多少次呢?因为第k+1次排序时前k个数已经有序了,这个时候在前k个数中有多少个不同且比第k+1个数大的数那么就会交换多少次,这个很容易讨论。关键是第k+1个数大于前k个数的最大值时我们需要多加思考一下,因为我们在对第一个数排序时,就会把第k+1个数放置在第一个位置,那么对于第一个位置的前k次排序时不会涉及到第k+1个数,但是当拿前k个数的最大值与第k+1个数比较时还会交换一次,所以会多交换一次,而且被交换到第k+1个位置上的数在对前k个数进行交换时不会再涉及到,这个时候我们可以把第k+1个数作为一个新的最大值进行考虑,并不影响中间过程的交换次数,但是当对第k+1个位置排序时还会涉及到一次,所以共涉及到2次,这是在前k个的最大值只出现一次的情况,如果前k次的最大值不只是出现一次,那么我们能够发现,由于原来前k个数中有多个最大值,而最大值与最大值是不会进行交换的,但是现在最大值变得更大了,因为最大值第一次出现的位置在一开始就挪至第k+1个数的位置了,那么从第二次出现最大值的位置往后,每个数都会多交换一次就是在遍历到这个位置的时候会多交换一次,这个答案我们必须要记录。这个地方需要找个数组仔细模拟一下。
细节见代码:
- #include<iostream>
- #include<algorithm>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #include<vector>
- #include<queue>
- using namespace std;
- const int N=2e5+10;
- int a[N],c[N];
- int lowbit(int x)
- {
- return x&-x;
- }
- void add(int x)
- {
- for(int i=x;i<N;i+=lowbit(i))
- c[i]++;
- }
- int sum(int x)
- {
- int ans=0;
- for(int i=x;i;i-=lowbit(i))
- ans+=c[i];
- return ans;
- }
- int main()
- {
- int T;
- cin>>T;
- while(T--)
- {
- int n;
- scanf("%d",&n);
- for(int i=1;i<=n;i++) c[i]=0;
- for(int i=1;i<=n;i++)
- scanf("%d",&a[i]);
- long long ans=0;
- printf("%lld",ans);
- int mx=a[1];
- add(a[1]);
- int cnt=1;//记录当前树状数组中有多少个不重复元素
- vector<int>p;//记录最大值出现的位置
- p.push_back(1);
- for(int i=2;i<=n;i++)
- {
- if(a[i]<=mx)
- {
- ans+=cnt-sum(a[i]);
- if(a[i]==mx)
- p.push_back(i);
- }
- else
- {
- if(p.size()==1)
- ans+=2;
- else
- ans+=2+i-p[1];
- mx=a[i];
- p.clear();
- p.push_back(i);
- }
- if(sum(a[i])-sum(a[i]-1)==0)
- add(a[i]),cnt++;
- printf(" %lld",ans);
- }
- puts("");
- }
- return 0;
- }

样例输入:
- 2
- 5
- 1 10 100 1000 10000
- 1 2 1 1 1
- 1 2
- 1 3
- 2 4
- 2 5
- 5
- 1 10 100 1000 10000
- 1 3 1 1 1
- 1 2
- 1 3
- 2 4
- 2 5
样例输出:
- 10101
- 10111
题意:给定一个有n个节点的有根树,根节点为1,每个节点i上有a[i]只苍蝇,还有一个对应的反应时间t[i],有个人一开始位于根节点,如果一旦人第一次到达i节点,那么i节点的所有子节点j上的苍蝇会在时间t[j]后飞走,人每到达一个节点,如果这个节点的苍蝇还没飞走就会把所有的苍蝇全部抓住,当反应时间还剩0s时人到达了,那么是人先抓苍蝇,问人最多能够抓到的苍蝇数目。
每个苍蝇的反应时间是小于等于3的。
分析:我们先来看一下这个3是干什么的。

以这个图为例,我们从1->2->1花费时间就是2s,那么我们下一秒从1->3需要花费1s, 那么总时间就是3s,也就是说我们对于每一个节点最多抓到他两个子节点上的苍蝇,前提还得是第二个节点的苍蝇的反应时间是3s,否则只能抓到一个节点的苍蝇。那么很显然如果1有更多的子节点,则其他子节点上的苍蝇都要飞走了,但其子节点的子节点上的苍蝇并不会受到影响,我们来看一下这样操作后对2号节点和3号节点的影响,因为我们是先到达了一次2节点,那么等我们返回1后,等下次再到2号节点时,会发现2号节点的所有子节点上的苍蝇都已经飞走了,而我们到达3号节点后我们就可以继续抓其子节点上的苍蝇。
树形DP含义:
f[i][0]代表第i个结点无贡献且其子节点苍蝇全部飞走的贡献
f[i][1]代表第i个结点无贡献但其子节点全部未受干扰的贡献
那么答案就是a[1]+f[1][1].
如果当前节点的子节点中有反应时间为3s的子节点,那么我们就有可能存在上述情况,否则是不会存在上述回溯的过程的,如果不存在回溯的过程,我们直接选择一个子节点权值最大的点进行递归即可,否则我们需要找到上图中的2号点和3号点,对于除了这两个点之外的其他子节点j我们都是取f[j][1],因为我们还没有到达子节点j,那么j的子节点是不可能受到干扰的,而3号点由于是后到达的,那么我们要加上3号点的权值,也等价于3号点的子节点是不受到干扰的,因为刚开始干扰,而且下一秒我们就开始考虑3号点的子节点了,所以这里我们只需要多加一个3号点的点权即可。但是2号节点不一样,如果我们不访问2号节点的话,那么2号点的贡献是f[2][1],但是我们访问2号点之后2号点的贡献就是a[2]+f[2][0],所以新增的贡献就是a[2]+f[2][0]-f[2][1],我们只需要记录最大值次大值及其编号即可,而对于子节点反应时间为3s的节点我们也需要统计一下点权的最大值和次大值及其对应的编号即可,为什么都需要记录次大值呢?防止2号点和3号点的最大值对应的点是同一个,我们求出来的这两个贡献取一个加和最大值计入贡献即可。把最大贡献加入f[x][1]即可。
更新f[x][0]:因为x的所有子节点都已经飞走,那么我们什么时候到达其子节点意义不大,我们子需要取其子节点j中f[j][0]和f[j][1]的最大值即可。
更新f[x][1]:一开始默认所有的子节点j都取f[j][0]和f[j][1]的最大值即可,因为一开始也是默认子节点都不进入,我们只是最后选择两个节点来进行更新最优值,最后直接把新加的贡献计入f[x][1]即可。
细节见代码:
- #include<iostream>
- #include<algorithm>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #include<vector>
- #include<queue>
- using namespace std;
- const int N=2e5+10;
- int h[N],e[N],ne[N],idx;
- int a[N],t[N];
- long long f[N][2];
- vector<int> p[N];//p[i]记录第i个结点的子节点中时间等于3的结点
- //f[i][0]代表第i个结点无贡献且其子节点苍蝇全部飞走的贡献?
- //f[i][1]代表第i个结点无贡献但其子节点全部未受干扰的贡献
- int read()
- {
- int x=0,f=1;
- char c=getchar();
- while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
- while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
- return x*f;
- }
- void add(int x,int y)
- {
- e[idx]=y;
- ne[idx]=h[x];
- h[x]=idx++;
- }
- void dfs(int x,int fa)
- {
- f[x][0]=f[x][1]=0;
- int mx1=0,id1=-1;//记录子节点为3s的节点的点权的最大值和对应的编号
- int mx2=0,id2=-1;//记录子节点为3s的节点的点权的次大值和对应的编号
- int mx3=0,id3=-1;//记录子节点的a[j]+f[j][0]-f[j][1]的最大值和对应的编号
- int mx4=0,id4=-1;//记录子节点的a[j]+f[j][0]-f[j][1]的次大值和对应的编号
- int mx=0;//记录子节点点权最大值
- // priority_queue<pair<long long,int> >q;
- for(int i=h[x];i!=-1;i=ne[i])
- {
- int j=e[i];
- if(j==fa) continue;
- dfs(j,x);
- if(t[j]==3)
- {
- if(a[j]>mx1)
- {
- mx2=mx1;
- id2=id1;
- mx1=a[j];
- id1=j;
- }
- else if(a[j]>mx2)
- {
- mx2=a[j];
- id2=j;
- }
- }
- mx=max(mx,a[j]);
- if(a[j]+f[j][0]-f[j][1]>mx3)
- {
- mx4=mx3;
- id4=id3;
- mx3=a[j]+f[j][0]-f[j][1];
- id3=j;
- }
- else if(a[j]+f[j][0]-f[j][1]>mx4)
- {
- mx4=a[j]+f[j][0]-f[j][1];
- id4=j;
- }
- f[x][0]+=f[j][1];
- f[x][1]+=max(f[j][0],f[j][1]);
- }
- long long ans=f[x][1]+mx;
- if(id1!=-1)//代表子节点中有3s的
- {
- if(id3!=id1)
- ans=max(ans,f[x][1]+mx3+mx1);
- else
- {
- if(id2==-1)//3s的孩子结点只有一个
- ans=max(ans,f[x][1]+mx4+mx1);
- else
- ans=max(ans,f[x][1]+max(mx1+mx4,mx2+mx3));
- }
- }
- f[x][1]=ans;
- }
- int main()
- {
- int T;
- cin>>T;
- while(T--)
- {
- idx=0;
- int n;
- scanf("%d",&n);
- for(int i=1;i<=n;i++)
- scanf("%d",&a[i]),h[i]=-1;
- for(int i=1;i<=n;i++)
- scanf("%d",&t[i]);
- for(int i=1;i<n;i++)
- {
- int u,v;
- scanf("%d%d",&u,&v);
- add(u,v);add(v,u);
- }
- dfs(1,-1);
- printf("%lld\n",f[1][1]+a[1]);
- }
- return 0;
- }

样例输入:
- 5
- 4
- 1 -3 2 -4
- 11
- 91 66 73 71 32 83 72 79 84 33 93
- 12
- 91 66 73 71 32 83 72 79 84 33 33 93
- 13
- 91 66 73 71 32 83 72 79 84 33 33 33 93
- 1
- 0
样例输出:
- 10
- 713
- 746
- 779
- 0
题意:给定一个长度为n的数组,你可以把这个数组看成是一个环形的,每次我们选择两个数进行合并,合并后的结果就是前一个数减去后一个数,问最后剩一个数时这个数最大能是多少。
分析:先假设我们最后剩余的数的位置是i,那么我们让第i个位置放在第一个位置,按照这个顺序对原数组进行一次重排,不妨设排序后的数组就是a[1~n]。
我们现在有a[1] a[2] …… a[n]
我们现在就等价于在每一个位置前放一个符号,然后进行运算得到最后的结果就行。
那么每一个位置前面的正负号是随意的吗?答案是不完全是,除了前两个数之外的数的正负号可以随意加,因为第一个数前面必须是+,而第二个数前面必须是-,这是运算规则决定的,但是对于第i个数,如果我们想让其为+,那么我们可以先让其与第i-1个数进行一次操作,然后就变为了正号,最后直接从前往后依次选择相邻的两个数进行操作即可。
比如我们现在想构造a[1]-a[2]-a[3]+a[4]+a[5]
那么我们从第2个数往后考虑,考虑前面是正号的数,那么发现第一个数是a[4],那么就先让他与前面一个数进行一次操作得到 a[1] a[2] a[3]-a[4] a[5]
发现a[5]前面也是正数,所以我们也让a[5]与其前面的数a[3]-a[4]进行一次操作得到a[1] a[2] a[3]-a[4]-a[5],剩下的都是负号了,我们直接从前往后依次选择两个数进行操作即可。
得到a[1]-a[2]-(a[3]-a[4] a[5])=a[1]-a[2]-a[3]+a[4]+a[5]
所以我们最后只需要选择两个相邻的数前一个取正号,后一个取负号,其余的数全部取绝对值即可,最后直接暴力枚举最大值即可。
下面是代码:
- #include<iostream>
- #include<algorithm>
- #include<cstring>
- #include<cstdio>
- #include<cmath>
- #include<vector>
- #include<queue>
- using namespace std;
- const int N=1e6+10;
- int a[N];
- int main()
- {
- int T;
- cin>>T;
- while(T--)
- {
- int n;
- scanf("%d",&n);
- long long s=0,ans=-0x3f3f3f3f3f3f3f3f;
- for(int i=1;i<=n;i++)
- scanf("%d",&a[i]),s+=abs(a[i]);
- if(n==1)
- {
- printf("%d\n",a[1]);
- continue;
- }
- for(int i=1;i<=n;i++)
- ans=max(ans,s-abs(a[i])-abs(a[i%n+1])+(a[i]-a[i%n+1]));
- printf("%lld\n",ans);
- }
- return 0;
- }