@happyforever
2018-11-06T12:06:48.000000Z
字数 7259
阅读 485
清北学堂 解题报告
期望得分:
实际得分:
后悔之前没有认真练习树状数组求逆序对。。
然后考场上用半小时出来了hhhhhhhhh
天才儿童nlh
然后得意忘形把离散化数组和树状数组空间开小了
挂成分暴力
日

我暴力代码常数巨大所以暴力分全挂了
的分因为的最后一次贡献没有考虑所以输出比答案少就很难受。
题意读错浪费了
最后还剩分钟的时候开始写,总共用了分钟写完

/** 求逆序对???* 求f(x)>g(y)同时x<y的点对(x,y)个数** 60分n^2枚举** 离散化+树状数组求逆序对?* 插入的时候插入g[i]* 查询的时候查询f[i]** ....* 天才儿童nlh* 现场YY出来树状数组求逆序对还行。* 可把我给牛逼坏了。*/#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;}inline int lb(int x){return x&(-x);}const int N=1e5+1;int n,k,tot,f[N],g[N],tr[N],a[N];long long ans;inline int ksm(long long x,int b){long long res=1;for(;b;b>>=1,x=x*x%k)if(b&1)res=res*x%k;return res;}inline void add(int x){for(;x<=tot;x+=lb(x))++tr[x];}inline int sum(int x){int res=0;for(;x;x-=lb(x))res+=tr[x];return res;}int main(){freopen("calc.in","r",stdin);freopen("calc.out","w",stdout);n=read(),k=read();for(int x,i=1;i<=n;++i){x=read();f[i]=ksm(i,x);g[i]=ksm(x,i);a[++tot]=f[i];a[++tot]=g[i];}std::stable_sort(a+1,a+1+tot);for(int i=1;i<=n;++i){f[i]=std::lower_bound(a+1,a+1+tot,f[i])-a;g[i]=std::lower_bound(a+1,a+1+tot,g[i])-a;}for(int i=n;i;--i){ans+=sum(f[i]-1);add(g[i]);}printf("%I64d",ans);fclose(stdin);fclose(stdout);return 0;}

/** 20分枚举每次给哪个系统升级* 另外20分r=0考虑有n个不同长度的线段,从下往上扫描,每次将所有等于当前高度的线段增高一单位,总共有k单位额外线段** 其他情况:* 首先把所有的防御塔变成同样的级别* 而后如果能将所有的都升级就全都升级* 否则考虑升级第r+1,3r+2,5r+3,...最后如果((2(x+1)-1)r+x+1)>n>((2x-1)r+x),那么也就是说最后会有一部分位置无法被覆盖,此时我们再从后面往前升级防御塔。。。* 不对。* 好像最后几个很难讨论的样子。* 细节好多啊我操。。。** 写完暴力。* 滚了滚了。*/#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=1e5+1;int n,r,a[N],vis[11],emp[11];long long k,ans;inline int min(int x,int y){return x<y?x:y;}inline int max(int x,int y){return x>y?x:y;}void dfs(int now){if(now>k){for(int i=1;i<=n;++i)emp[i]=0;long long minn=1e18+1;for(int i=1;i<=n;++i)for(int j=max(1,i-r);j<=min(n,i+r);++j)emp[j]+=a[i];for(int i=1;i<=n;++i)if(minn>emp[i])minn=emp[i];if(minn>ans)ans=minn;return ;}for(int i=1;i<=n;++i){++a[i];dfs(now+1);--a[i];}}int main(){freopen("game.in","r",stdin);freopen("game.out","w",stdout);n=read(),r=read();scanf("%I64d",&k);for(int i=1;i<=n;++i)a[i]=read();if(n<=10)dfs(1);else{std::sort(a+1,a+1+n);int tmp,cnt=1,emp=a[1],i=2;for(;i<=n && a[i]==a[i-1];++i)++cnt;for(;i<=n;++i){tmp=cnt*(a[i]-emp);if(k>tmp)k-=tmp;else break;for(;i<n && a[i+1]==a[i];++i)++cnt;emp=a[i],++cnt;}ans=emp,k/=cnt;if(k)ans+=k-1;}printf("%I64d",ans);fclose(stdin);fclose(stdout);return 0;}


/** 对每个联通块维护有哪些边,把这些边存起来然后sort一发,之后记录前缀和* 每次询问的时候考虑对每个联通块分别求出最大能到哪个w,答案就是这个位置的前缀和*** 。。。* woc理解错题意了* 完犊子* 赶紧上暴力算了。** 应该能拿个30分?*/#include <cstdio>#include <cstring>#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=1e5+1;int n,m,k,q,tot,head[N];long long ans,emp;bool vis[N];struct Edge{int v,w,nxt;}edge[N<<1];inline void add(int w,int v,int u){edge[++tot]=(Edge){v,w,head[u]};head[u]=tot;edge[++tot]=(Edge){u,w,head[v]};head[v]=tot;}void dfs(int now){vis[now]=true;for(int v,i=head[now];i;i=edge[i].nxt)if(edge[i].w<=q){emp+=edge[i].w;if(!vis[v=edge[i].v])dfs(v);}}int main(){freopen("graph.in","r",stdin);freopen("graph.out","w",stdout);n=read(),m=read(),k=read();for(int i=1;i<=m;++i)add(read(),read(),read());while(k--){q=read(),ans=0;memset(vis,false,sizeof vis);for(int i=1;i<=n;++i)if(!vis[i]){emp=0,dfs(i);emp>>=1;if(emp>ans)ans=emp;}printf("%I64d\n",ans);}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;}inline int lb(int x){return x&(-x);}const int N=1e5+1;int n,k,tot,f[N],g[N],tr[N<<1],a[N<<1];long long ans;inline int ksm(long long x,int b){long long res=1;for(;b;b>>=1,x=x*x%k)if(b&1)res=res*x%k;return res;}inline void add(int x){for(;x<=tot;x+=lb(x))++tr[x];}inline int sum(int x){int res=0;for(;x;x-=lb(x))res+=tr[x];return res;}int main(){freopen("calc.in","r",stdin);freopen("calc.out","w",stdout);n=read(),k=read();for(int x,i=1;i<=n;++i){x=read();f[i]=ksm(i,x);g[i]=ksm(x,i);a[++tot]=f[i];a[++tot]=g[i];}std::stable_sort(a+1,a+1+tot);for(int i=1;i<=n;++i){f[i]=std::lower_bound(a+1,a+1+tot,f[i])-a;g[i]=std::lower_bound(a+1,a+1+tot,g[i])-a;}for(int i=n;i;--i){ans+=sum(f[i]-1);add(g[i]);}printf("%I64d",ans);fclose(stdin);fclose(stdout);return 0;}
二分答案+贪心
每次的时候从前往后枚举所有位置,贪心策略:如果有某个位置的防御级别小于当前的答案那么就将位置的防御塔都升级一次,此时的所有位置都会增加的防御级别,虽然前面的也会影响到,但是由于我们只是在判断一个答案是否可行,而前面的已经判断过可行所以再加上一些防御等级依然是可行的,就不需要管了
对于上面的"所有位置都增加的防御级别",如果暴力维护的话最坏是的,那么总体复杂度就是,有点太看不起的数据了
所以考虑每次时维护一个差分数列,那么"所有位置都增加的防御级别"也就是在的位置差分数组标记为就行了
这样就可以优化成每次的
总复杂度
#include <cstdio>#include <algorithm>inline long long read(){long long n=0;register char c=getchar();while(c<'0' || c>'9')c=getchar();while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();return n;}typedef long long LL;const int N=5e5+5;LL n,R,k,ans,sum[N],Sum[N],a[N],b[N];inline LL min(LL x,LL y){return x<y?x:y;}inline LL max(LL x,LL y){return x>y?x:y;}inline bool judge(LL x){LL tot=0,delta,r;for(int i=0;i<=n;++i)Sum[i]=sum[i],b[i]=0;for(int i=1;i<=n;++i){b[i]+=b[i-1],Sum[i]+=b[i];if(Sum[i]<x){delta=x-Sum[i];b[i]+=delta;tot+=delta;if(tot>k)return false;r=min(n,i+R+R);b[r+1]-=delta;}}return true;}int main(){freopen("game.in","r",stdin);freopen("game.out","w",stdout);n=read(),R=read(),k=read();for(int i=1;i<=n;++i)a[i]=read();LL l,r;for(int i=1;i<=n;++i){l=max(1ll,i-R),r=min((LL)n,i+R);sum[l]+=a[i],sum[r+1]-=a[i];}for(int i=1;i<=n;++i)sum[i]+=sum[i-1];l=0,r=1e18+5e14;LL mid;while(l<=r){mid=l+r>>1;if(judge(mid))l=mid+1;else r=mid-1;}printf("%I64d",l-1);fclose(stdin);fclose(stdout);return 0;}
套路题
每次询问,大的可以通过小的再加一些边之后推出,所以考虑离线做法:先把所有的询问读入,按照从小到大排序之后再处理
那么同样由于大的询问可以通过小的询问推出,考虑把所有边按照边权从小到大排序
考虑用并查集维护答案,对每个并查集维护其边权和数组:
对于每条合法的边,如果这条边的两个端点原本不联通,那么可以将加到里,并且将的父亲指向
当然不要忘了将这两个点之间的这条边的权值加到里面
一定要路径压缩!(小明没有路径压缩挂成50分其他全TLE了)
#include <cstdio>#include <cstring>#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=1e5+1;int n,m,k,q,fa[N],ans[N],size[N];bool vis[N];struct Edge{int u,v,w;bool operator<(const Edge &gg)const{return w<gg.w;}}edge[N];struct Query{int w,id;bool operator<(const Query &gg)const{return w<gg.w;}}que[N];int find(int x){if(fa[x]!=x)fa[x]=find(fa[x]);return fa[x];}int main(){freopen("graph.in","r",stdin);freopen("graph.out","w",stdout);n=read(),m=read(),k=read();for(int i=1;i<=n;++i)fa[i]=i;for(int i=1;i<=m;++i)edge[i]=(Edge){read(),read(),read()};for(int i=1;i<=k;++i)que[i]=(Query){read(),i};std::sort(que+1,que+1+k);std::sort(edge+1,edge+1+m);long long maxn=0;int id=1,fu,fv;for(int i=1;i<=k;++i){if(que[i].w!=que[i-1].w)for(;id<=m && edge[id].w<=que[i].w;++id){fu=find(edge[id].u),fv=find(edge[id].v);if(fu!=fv)fa[fu]=fv,size[fv]+=size[fu];size[fv]+=edge[id].w;if(size[fv]>maxn)maxn=size[fv];}ans[que[i].id]=maxn;}for(int i=1;i<=k;++i)printf("%d\n",ans[i]);fclose(stdin);fclose(stdout);return 0;}