【杂题1】USACO 2018 Open Contest-练习

https://www.xoj.red/contests/show/1231

下面会写一些题目的解析什么的,当然不会粘贴题目只是简单提一下

 (部分题目简单的题目就不概括了)

其实难度应该前面比较低.

 

问题A: 3894: Out of Sorts

本题求出冒泡排序需要几趟。

考虑一次冒泡排序的交换,减小对应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;
}
问题A: 3894: Out of Sorts

问题B: 3895: Lemonade Line

考虑一个简单的贪心,每次选择当前等待奶牛数目最多的一个,占用等待奶牛数少的奶牛的位置。

如果不是这样做,对答案会有正贡献,是我们不期望的,与答案最优性矛盾,我们选择一个一定是最优的决策。

# 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;
}
问题B: 3895: Lemonade Line

 问题C: 3896: Multiplayer Moo

大概是不存在一个正解的。

 问题D: 3897: Out of Sorts

考虑这样一个事情,如果正反来了一边那么每一次对于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;
}
问题D: 3897: Out of Sorts

 问题E: 3898: Milking Order

本题要求最大化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;
}
问题E: 3898: Milking Order

 问题F: 3899: Talent Show

考虑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;
}
问题F: 3899: Talent Show

 问题G: 3900: Out of Sorts

我们不妨宏观考虑这个排序的问题,在对于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;
}
问题G: 3900: Out of Sorts

 问题I: 3902: Disruption

 这个题目非常有价值,我想专门开一个blog讲一讲这个题。

 https://www.cnblogs.com/ljc20020730/p/10467324.html 

欢迎支持!!!

posted @ 2019-03-01 18:37  ljc20020730  阅读(227)  评论(0编辑  收藏  举报