【杂题1】USACO 2018 Open Contest-练习
https://www.xoj.red/contests/show/1231
下面会写一些题目的解析什么的,当然不会粘贴题目只是简单提一下
(部分题目简单的题目就不概括了)
其实难度应该前面比较低.
本题求出冒泡排序需要几趟。
考虑一次冒泡排序的交换,减小对应1个位子上的1个逆序对。
但是对于每一个位子所需要减小的逆序对数量是不一样的。
对于每一趟,消去每一个位子上1个逆序对
所以趟数就是每个位子上的数产生逆序对数的最大值。
最后的+1指的是即使上一次已经消除所有逆序对了,我们并不知道数组有序了,所以判断最后一遍查看是否有序。
考虑树状数组维护$O(n log_2 n)$
和善的树状数组代码如下:
# include <bits/stdc++.h> using namespace std; const int N=1e5+10; int a[N],tmp[N],c[N]; int n,T; void update(int x){for (;x<=n;x+=x&-x)c[x]++;} int query(int x){int ret=0;for (;x;x-=x&-x) ret+=c[x];return ret;} int main() { scanf("%d",&n); tmp[0]=n; for (int i=1;i<=n;i++) scanf("%d",&a[i]),tmp[i]=a[i]; sort(tmp+1,tmp+tmp[0]+1); T=unique(tmp+1,tmp+1+tmp[0])-tmp-1; int ans=0; for (int i=1;i<=n;i++) { int w=lower_bound(tmp+1,tmp+1+T,a[i])-tmp; update(w); ans=max(i-query(w),ans); } printf("%d\n",ans+1); return 0; }
考虑一个简单的贪心,每次选择当前等待奶牛数目最多的一个,占用等待奶牛数少的奶牛的位置。
如果不是这样做,对答案会有正贡献,是我们不期望的,与答案最优性矛盾,我们选择一个一定是最优的决策。
# include <bits/stdc++.h> using namespace std; const int N=1e5+10; int n,a[N]; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+1+n); int cnt=0,Ans=0; for (int i=n;i>=1;i--) { if (cnt<=a[i])Ans++,cnt++; else break; } printf("%d\n",Ans); return 0; }
大概是不存在一个正解的。
考虑这样一个事情,如果正反来了一边那么每一次对于i位置上的数a[i]来说他左边位置j $\in $ [1,i-1]比a[i]严格大的数有一个被放到了i的右边
他右边的位置j$\in $ [i+1,n]比a[i]严格小的数有一个被放到了i的左边。当且仅当a[i]左边和右边不存在上述的数那么停止。
如果把一个数组变为有序显然第i个位子上放置第i大的数。
显然他们的位置一定在i位置后面。所有至少需要移动这么多次,把每一个比i大的数移动到i的右面。
显然我们冒泡1次对于每个i对应的有多少数在他左边只能减少1个这样的数,所以答案就是max{前i个数里面有多少个数字离散化后是比i大的,1}
# include <bits/stdc++.h> using namespace std; const int N=1e5+10; int c[N],n; struct rec{int x,id;}a[N]; bool cmp(rec a,rec b) { if (a.x!=b.x) return a.x<b.x; return a.id<b.id; } void update(int x){for (;x<=n;x+=x&-x) c[x]++;} int query(int x){int ret=0; for (;x;x-=x&-x) ret+=c[x]; return ret;} int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i].x),a[i].id=i; sort(a+1,a+1+n,cmp); int ans=1; for (int i=1;i<=n;i++){ update(a[i].id); ans=max(ans,i-query(i)); } printf("%d\n",ans); return 0; }
本题要求最大化pos满足前面若干条边连起来是一个无环图。
求最优遍历顺序,想到拓扑排序check+二分答案,然后手打堆优化STL常数。
先存输入,每一次二分答案位置pos,把前面pos个连边系统前面连到后面,然后跑拓扑,每次选择入度为0,标号最小的一个遍历
这里用到heap了,然后使用priority_queue在不开O2情况下TLE,然后就写了一个手写堆。
# include <bits/stdc++.h> using namespace std; const int N=1e5+10,M=2e5+10; int head[N],ind[N],ans[N]; bool vis[N]; vector<int>ipt[N]; int n,m,tot; struct rec{int pre,to;}a[M]; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } inline void write(int x) { if (x>9) write(x/10); putchar('0'+x%10); } void adde(int u,int v) { ind[v]++; a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } struct heap{ # define lson (x<<1) # define rson ((x<<1)+1) # define fath (x>>1) int Size,c[N<<1]; heap(){Size=0;} void down(int x) { while (x<Size) { if (lson>Size) break; int t=lson; if (rson<=Size&&c[rson]<c[lson]) t=rson; if (c[x]<=c[t]) break; swap(c[x],c[t]); x=t; } } void up(int x) { while (x) { if (c[x]>c[fath]) break; swap(c[x],c[fath]); x=fath; } } int top(){return c[1];} void push(int x){c[++Size]=x;up(Size);} void pop() {swap(c[1],c[Size--]);down(1);} bool empty(){ return !Size;} }q; bool check(int Mid) { tot=0; memset(vis,false,sizeof(vis)); memset(head,0,sizeof(head)); memset(ind,0,sizeof(ind)); for (int i=1;i<=Mid;i++) for (int j=1;j<ipt[i].size();j++) adde(ipt[i][j-1],ipt[i][j]); for (int i=1;i<=n;i++) if (ind[i]==0) q.push(i); ans[0]=0; while (!q.empty()) { int u=q.top(); q.pop(); vis[u]=true; ans[++ans[0]]=u; for (int i=head[u];i;i=a[i].pre){ int v=a[i].to; if (ind[v]==0) return false; ind[v]--; if (ind[v]==0) q.push(v); } } for (int i=1;i<=n;i++) if (!vis[i]) return false; return true; } int main() { n=read();m=read(); for (int i=1;i<=m;i++) { int r=read(); for (int j=1;j<=r;j++) { int t=read(); ipt[i].push_back(t); } } int L=1,R=m,Ans=0; while (L<=R) { int mid=(L+R)>>1; if (check(mid)) Ans=mid,L=mid+1; else R=mid-1; } if (Ans==0) { for (int i=1;i<=n;i++) write(i),putchar(' '); putchar('\n'); return 0; } check(Ans); for (int i=1;i<=n;i++) write(ans[i]),putchar(' '); putchar('\n'); return 0; }
考虑01分数规划,即二分答案套dp检查。
设答案$x\leq \frac{\sum w_i}{\sum t_i} $移项可得$\sum t_i - x \sum w_i \geq 0$
即要求我们对于给定参数x,求一组$w_i$和$t_i$满足$\sum t_i - x \sum w_i \geq 0$
即$max\{ \sum t_i - x \sum w_i \} \geq 0$
对于第i头奶牛的贡献就变成一个确定的数了,即$t_i-xw_i$
考虑01背包。
设f[x]表示当前选择总重量为x,$\sum t_i - x \sum w_i $的最大值。
考虑从x转移出去对x+w[i]有影响,有转移方程$f[x+w[i]]=max(f[x+w[i]],f[x]+t_i-xw_i)$
然后要求总重量超过W,事实上我们并不关心到底超过W多少,只要超过就行,那么就统计在恰好的地方
即统计在f[W]处,那么f[W]就是答案。
# include <bits/stdc++.h> # define inf (1<<30) using namespace std; const int N=1e5+10; double f[N]; int w[N],t[N]; int n,W; bool check(double x) { f[0]=0; for(int i=1;i<=W;i++) f[i]=-inf; for (int i=1;i<=n;i++) for (int j=W;j>=0;j--) f[min(j+w[i],W)]=max(f[min(j+w[i],W)],f[j]+t[i]-x*w[i]); return f[W]>=0.0; } int main() { scanf("%d%d",&n,&W); for (int i=1;i<=n;i++) scanf("%d%d",&w[i],&t[i]); double l=0,r=inf,ans; while (r-l>=(1e-5)){ double mid=(l+r)/2.0; if (check(mid)) ans=mid,l=mid; else r=mid; } ans=ans*1000; printf("%d\n",(int)ans); return 0; }
我们不妨宏观考虑这个排序的问题,在对于A进行子冒泡排序的时候,可以发现,比第i个元素小的元素且在i右边的元素是以1贡献/次向i移动的。
所以第i个元素和第i+1元素之间的隔板产生的区间长度就是$ (maxpos-i+1-1)=maxpos-i $,其中$ maxpos $指的是排序以后比$a_i$元素更小的,但排在$a_i$元素的右边,且距离$a_i$最远的数$a_j$的下标(位置)$j$。
对于i被计算1次当他的前面隔板不是连续的,或者 后面隔板不是连续的满足其一即可,所以若把i存在于的这个隔板不连续的区间排成有序,那么仅对于元素i它被算的次数(产生的贡献)是$max(t_{i-1},t_i)$
说明一下: 如果i前后隔板都不连续的话i被产生$max(t_{i-1},t_i)$后便不产生贡献了,如果前面或后面隔板不连续显然。
有了上面的说明,这个题目的算法呼之欲出,
1. 遍历原数组,当前位置为i,求出$ t_i $,查找最后一个比$a_i$小的数出现的位置为j,令$ t_i = j - i $
2. 求出 $ans = \sum\limits_{i=1} ^ {n} \max \{ t_{i-1}, t_i \}$ 并输出。
显然按照上面模拟是$ O(n^2) $算法。
我们可以这样做,先把$a_i$每个元素记录它的值$val$和下标$i$,排序。
那么求一个前缀$ maxpos=max{a[i].id , maxpos }$,显然遍历到i位置的时候,前面都是比$ a[i] $ 的值,然后前面最大的$ id $号码就是$ maxpos $。
那么时间复杂度就降到$ O(nlog_2n) $
# include <bits/stdc++.h> # define int long long using namespace std; const int N=1e5+10; struct rec{ int val,id; }a[N]; int n,t[N]; bool cmp(rec a,rec b) { if (a.val!=b.val) return a.val<b.val; else return a.id<b.id; } signed main() { scanf("%lld",&n); for (int i=1;i<=n;i++) scanf("%lld",&a[i].val),a[i].id=i; sort(a+1,a+1+n,cmp); int maxpos=0; for (int i=1;i<=n;i++) { maxpos=max(maxpos,a[i].id); t[i]=max(1ll,maxpos-i); } int ans=0; for (int i=1;i<=n;i++) ans+=max(t[i],t[i-1]); cout<<ans; return 0; }
这个题目非常有价值,我想专门开一个blog讲一讲这个题。
https://www.cnblogs.com/ljc20020730/p/10467324.html
欢迎支持!!!