@happyforever
2018-10-30T13:32:12.000000Z
字数 8437
阅读 502
清北学堂 解题报告

期望得分:
实际得分:
智尚锁
智商下线
降智打击
神题
特判想了
wby还写了个证明。。。
单调性神题。
最开始Two Point的暴力写挂了,调了二十来分钟放弃掉重新写了一份近似的暴力
正解敲了左右
不知道为啥大样例。。。
很难受
只有白送的分

/** qndyd* https://blog.csdn.net/queuelovestack/article/details/52571408*/#include <cstdio>int main(){freopen("lock.in","r",stdin);freopen("lock.out","w",stdout);long long L,R;scanf("%lld%lld",&L,&R);if(!L)L++;if(!R)R++;if(R-L>=2)printf("%lld",(R-L)/2+1);elseif(L==R){if(L==1)puts("0");elseif(L==2)puts("1");else puts("2");}else{if(L==1&&R==2)puts("1");else puts("2");}fclose(stdin),fclose(stdout);return 0;}

/** https://blog.csdn.net/qq_37920580/article/details/78025341* 清北日常原题。。。** 对每个点都求出以它为起点(或者是终点)的 长度不大于d的区间和* 考虑贪心,除非是无法使长度达到d,否则是一定要删掉长度为d的区间的* 那么我们的问题就在于:如何能够在一段区间内快速寻找到长度为d的和最大的子区间** 首先预处理出前缀和sum* 那么对于每个点k为终点的长度不大于d的子区间* 在1<=k<=d时子区间和为sum[k]* 而当d+1<=k<=n时子区间和则为sum[k]-sum[k-d]* 于是对于一段区间[l,r]假设最大长度为d的子区间和为delta* 那么我们需要判断的即是sum[r]-sum[l-1]-delta是否<=p** 单调递减队列,存入长度为d的区间的值* 对于每个点k为终点的讨论,都在队列中加入sum[k]-sum[k-d]使得讨论一定有解(标记位置为k-d+1)* 假设现在已经得到可以成立的区间[l,r]([l-1,r],[l,r+1]都不能使条件成立)* 注意此时的右端点全部是r+1* 在讨论r+1为右端点时,左端点直接从l开始枚举* 而且对于单调队列中的元素,左端点也一定要>=r+1* 由于队列为单调递减,所以第一个元素一定是当前区间中,长度为d的子区间的和的最大值* 由此可以得到希望的结果** 所以此时我们只需判断(sum[r+1]-sum[l-1]-队列第一个元素)是否<=p* 即可判定成立与否* 若成立,就可以直接更新结果,且以r+1为右端点的讨论终止* 若不成立,那么l+1,同时对当前单调队列的元素的左端点进行判定(左端点>=l+1)* 重复上述过程*/#include <cstdio>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;}inline int max(int x,int y){return x>y?x:y;}const int N=1e6+1;int n,d,ans;long long sum[N],que[N],pos[N],p;int main(){freopen("sequence.in","r",stdin);freopen("sequence.out","w",stdout);n=read(),p=read(),d=read();ans=d;for(int i=1;i<=n;++i)sum[i]=sum[i-1]+read();// if(d>=n){printf("%d",n);goto E;}long long res;if(n<=100)for(int l=2,r=d+1;r<=n;++l,++r){res=sum[r]-sum[l-1];for(int i=1;i<=l;++i)for(int j=r+1;j<=n;++j)if(sum[j]-sum[i-1]-res<=p)ans=max(ans,j-i+1);}else{int h=1,t=0,last_l=1;for(int i=d+1;i<=n;++i){res=sum[i]-sum[i-d];while(h<=t && res>=que[t])--t;que[++t]=res;pos[t]=i-d+1;while(sum[i]-sum[last_l-1]>p+que[h])++last_l;while(pos[h]<last_l)++h;ans=max(ans,i-last_l+1);}}printf("%d",ans);E: fclose(stdin),fclose(stdout);return 0;}

/**/#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=1e6+1;int n,m,l,r,ans,tot,a[N];bool vis[1001][1001];void add(int u,int v){vis[u][v]=1;}void change(int l,int r){for(int i=l;i<=r;++i)for(int j=i+1;j<=r;++j){if(vis[i][j])vis[i][j]=false,vis[j][i]=true;else vis[i][j]=true,vis[j][i]=false;}}void query(){for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)for(int k=j+1;k<=n;++k)if((vis[i][j] && vis[j][k] && vis[k][i]) || (vis[j][i] && vis[k][j] && vis[i][k]))++ans;}int main(){freopen("fight.in","r",stdin);freopen("fight.out","w",stdout);n=read(),m=read();if(m==0)return printf("0"),0;for(int i=1;i<=n;++i)a[i]=read();for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j){if(a[i]>a[j])add(i,j);elseif(a[j]>a[i])add(j,i);}for(int i=1;i<=m;++i){l=read(),r=read();change(l,r);}query();printf("%d",ans);fclose(stdin);fclose(stdout);return 0;}
第一次最多往第一个瓶子倒,第二次最多往第二个瓶子倒
第三次只能往第一个瓶子倒滴水,第四次最多往第二个瓶子倒滴水
最后答案如果是
注意特判某些情况:
时,若则输出,若那么输出,其他情况输出
剩下的就是,的时候输出,其他情况输出
#include<cmath>#include<cstdio>#include<vector>#include<cstring>#include<iostream>#include<algorithm>using namespace std;int main(){freopen("lock.in","r",stdin);freopen("lock.out","w",stdout);long long tmp,l,r,ans,sh;scanf("%lld%lld",&l,&r);// while(scanf("%lld%lld",&l,&r)!=EOF){if(l==1&&r==1){puts("0");return 0;}if(l<=2&&r<=2){puts("1");return 0;}ans=2;sh=l+2;if(sh>=r-1){puts("2");return 0;}// printf("%d\n",(-1)/4);tmp=r-1-sh;tmp=tmp/2;if((r-1-sh)%2!=0)tmp++;ans=ans+tmp;printf("%lld\n",ans);}return 0;}
对每个点都求出以它为起点(或者是终点)的 长度不大于d的区间和
考虑贪心,除非是无法使长度达到d,否则是一定要删掉长度为d的区间的
那么我们的问题就在于:如何能够在一段区间内快速寻找到长度为d的和最大的子区间
首先预处理出前缀和
那么对于每个点为终点的长度不大于的子区间
在时子区间和为
而当时子区间和则为
于是对于一段区间假设最大长度为d的子区间和为
那么我们需要判断的即是是否所以问题变成维护一段区间内的最大子段和
可以线段树,ST表
也可以单调队列
考虑Two Point法
有两个指针分别指向两个位置,那么这两个位置确定一个区间
考虑如果合法,那么右移直到
如果不合法,那么左移直到
可以发现一定是在不断右移的,不会存在不合法但是合法的情况
所以答案区间单调不降
单调递减队列,存入长度为的区间的值
对于每个点为终点的讨论,都在队列中加入使得讨论一定有解(标记位置为)
假设现在已经得到可以成立的区间(都不能使条件成立)
注意此时的右端点全部是
在讨论为右端点时,左端点直接从开始枚举
而且对于单调队列中的元素,左端点也一定要
由于队列为单调递减,所以第一个元素一定是当前区间中,长度为的子区间的和的最大值
由此可以得到希望的结果
所以此时我们只需判断(队列第一个元素)是否
即可判定成立与否
* 若成立,就可以直接更新结果,且以为右端点的讨论终止
* 若不成立,那么,同时对当前单调队列的元素的左端点进行判定(左端点)
* 重复上述过程
#include<cmath>#include<cstdio>#include<vector>#include<cstring>#include<iostream>#include<algorithm>using namespace std;long long sum[1010101],f[1010101],q[1010101],a[1010101];inline int read(){int ans=0,fu=1;char ch;for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') fu=-fu;for(;ch<='9'&&ch>='0';ch=getchar()) ans=ans*10+ch-'0';return ans*fu;}inline long long readl(){long long ans=0,fu=1;char ch;for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') fu=-fu;for(;ch<='9'&&ch>='0';ch=getchar()) ans=ans*10+ch-'0';return ans*fu;}int main(){freopen("sequence.in","r",stdin);freopen("sequence.out","w",stdout);int n,d,i,l,ans=0,r,h,t;long long p;n=read(),p=readl(),d=read();// scanf("%d%lld%d",&n,&p,&d);for(i=1;i<=n;i++){a[i]=read();sum[i]=sum[i-1]+a[i];}for(i=1;i+d-1<=n;i++)f[i]=sum[i+d-1]-sum[i-1];//,printf("%d %d\n",i,f[i]);l=0,r=1,h=0,t=0;if(d==1)q[t++]=1;for(l=1;l<=n;l++){if(h!=t&&q[h]<l)h++;while(r<=n&&(sum[r]-sum[l-1]-f[q[h]]<=p||r-l+1<=d)){r++;while(t-1>=h&&f[r-d+1]>f[q[t-1]])t--;if(r-d+1>0)q[t++]=r-d+1;}// printf("%d %d\n",l,r);ans=max(ans,r-l);}printf("%d\n",ans);return 0;}
实际是求图中最后的三元环个数
如果三个点不能构成三元环那么一定有一个人能打赢另外两个人
令表示第个人能打赢场
那么答案就是
问题转化为次操作后每个人能赢几场
但是依然不好做,依然需要枚举
注意到题目是先进行此翻转,最后每个人能赢几场
那么先进行哪次操作不会影响最终答案
考虑离线,记录所有的操作是什么能力值开始反转以及什么能力值结束反转
比如有一次操作,那么能力值为的时候出现反转,能力值为的反转结束
对于所有操作,位置记为,的位置记为
能力值为的能赢几场就是先将的进行反转,而后的操作相当于对一段区间
这个是一个经典的线段树区间维护的问题,考虑第个叶子表示能力值为的是否进行了反转
最后求答案的时候相当于求有多少叶子的值为
而能力值为的时候,先将的进行反转、将的也要进行反转,最后求:"有多少位置的叶子值为(原来能打过能力值为的但是经过反转打不过了) 有多少位置的叶子值为(原来打不过能力值为的现在依然打不过",引号内的内容就是
那么推广:枚举所有能力值为的,将进行反转,也将的进行反转,求有多少位置的叶子值为有多少位置的叶子值为
求出所有,最后
#include <vector>#include <cstdio>#include <algorithm>const int N=100001;int a[N],l[N],r[N];std::vector<std::pair<int,int> >s[N<<2];struct Node{int tag,w;}tree[N<<3];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;}#define ls root<<1#define rs root<<1|1#define mp std::make_pairvoid pushdown(int root,int left,int right){if(!tree[root].tag)return ;int mid=left+right>>1;tree[ls].tag^=1,tree[rs].tag^=1;tree[ls].w=mid-left+1-tree[rs].w;// printf("%d %d %d\n",left,right,tree[root*2].w);tree[rs].w=right-mid-tree[rs].w;tree[root].tag=0;}void update(int root,int left,int right,int x,int y){// printf("%d %d|%d %d\n",x,y,left,right);if(x<=left&&y>=right){// printf("%d\n",tree[root].w);tree[root].w=right-left+1-tree[root].w;tree[root].tag^=1;return ;}pushdown(root,left,right);int mid=left+right>>1;if(mid>=x)update(ls,left,mid,x,y);if(mid< y)update(rs,mid+1,right,x,y);tree[root].w=tree[ls].w+tree[rs].w;// printf("%d %d %d\n",left,right,tree[root].w);}int query(int root,int left,int right,int x){if(left==right)return tree[root].w;pushdown(root,left,right);int mid=left+right>>1;if(mid>=x)return query(ls,left,mid,x);else return query(rs,mid+1,right,x);}int main(){freopen("fight.in","r",stdin);freopen("fight.out","w",stdout);int tmp1,tmp2,c,m,ll,rr,tmp,n;long long ans=0;scanf("%d%d",&n,&m);for(int i=1;i<=n;++i)scanf("%d",&a[i]);std::sort(a+1,a+n+1);for(int i=1;i<=m;++i){scanf("%d%d",&l[i],&r[i]);tmp1=std::lower_bound(a+1,a+n+1,l[i])-a;tmp2=std::upper_bound(a+1,a+n+1,r[i])-a;--tmp2;s[tmp1 ].push_back(mp(tmp1,tmp2));s[tmp2+1].push_back(mp(tmp1,tmp2));}update(1,1,n,1,n);ans=(long long)n*(n-1)*(n-2)/6;for(int i=1;i<=n;++i){c=s[i].size();if(i!=1)update(1,1,n,i-1,i-1);for(int j=0;j<c;++j){ll=s[i][j].first;rr=s[i][j].second;// printf("%d %d %d|\n",i,ll,rr);if(ll<=rr)update(1,1,n,ll,rr);// printf("%d|\n",tree[1].w);}tmp=tree[1].w-query(1,1,n,i);// printf("%d %d %d\n",i,tmp,tree[1].w);ans=ans-((long long)tmp*(tmp-1)/2);}printf("%lld\n",ans);fclose(stdin);fclose(stdout);return 0;}