2017 清北济南考前刷题Day 4 morning

 

考场思路:

倒着算就是

可以对一个数-1

可以合并两个数

可以证明只有0和0才能执行合并操作

然后模拟

 

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

#define N 1000001

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

int a[N];

int main()
{
    //freopen("multiset.in","r",stdin);
//    freopen("multiset.out","w",stdout);
    int n;
    read(n); 
    int sum0=0,cnt=0,x;
    for(int i=1;i<=n;i++) 
    {
        read(x);
        if(!x) sum0++;
        else a[++cnt]=x;
    }
    sort(a+1,a+cnt+1);
    long long ans=0,gather=0;
    for(int i=1;i<=cnt;i++)
    {
        if(!a[i]) break;
        a[i]-=gather;
        x=a[i]; gather+=x; ans+=x;
        while(x) 
        {
            x--;
            if(sum0>1) sum0=sum0+1>>1;
            else break;
        }
        sum0++;
        while(i<cnt && a[i+1]-gather==0)
        {
            sum0++;
            a[++i]-=gather;
        }
    }
    while(sum0>1) ans++,sum0=sum0+1>>1;
    cout<<ans;
}
View Code

 

 

 

考场上没注意有向图。。。。

一条道路如果能在上一组,那么肯定把它放在上一组最优

所以可以没加一条边,就判断当前1和n是否联通

判断方式: u-->v若现在u没有与1联通,就不管他

若u和v都与1联通了,那也不管他

若 u与1联通,而v 没有联通,那就再从v开始bfs

这样 每条边只会被访问一次

 

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

#define N 200001
#define M 500001

int n;

int front[N],nxt[M],to[M],tot;

bool vis[N]; int use[N],cnt;

queue<int>q;

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void add(int u,int v)
{
    to[++tot]=v; nxt[tot]=front[u]; front[u]=tot;
}

bool bfs(int s)
{
    if(s==n) return true;
    while(!q.empty()) q.pop();
    q.push(s);
    int now;
    while(!q.empty())
    {
        now=q.front(); q.pop();
        for(int i=front[now];i;i=nxt[i])
            if(!vis[to[i]])
            {
                use[++cnt]=to[i];
                if(to[i]==n) return true;
                vis[to[i]]=true;
                q.push(to[i]);
            }
    }
    return false;
}

int main()
{
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    int m;
    read(n); read(m);
    int u,v; int ans=1;
    vis[1]=true;
    for(int i=1;i<=m;++i)
    {
        read(u); read(v);
        if(vis[u] && !vis[v]) 
        {
            add(u,v);
            vis[v]=true;
            use[++cnt]=v;
            if(bfs(v))
            {
            //    printf("%d\n",i);
                for(int i=1;i<=cnt;++i) vis[use[i]]=false,front[use[i]]=0;
                front[1]=0;
                cnt=tot=0; ans++;
                add(u,v);
                if(u!=1) use[++cnt]=u;
                if(u==1) vis[v]=true,use[++cnt]=v;
            }
        }
        else if(!(vis[u] && vis[v])) add(u,v),use[++cnt]=u;    
    }
    cout<<ans;
}
View Code

 

std思路:

结合了倍增的二分

如果用朴素的二分,会被m条边分m组卡成mm

先考虑1条边 能否使其联通,不能再考虑2条边,然后4条,8条……

若在2^p时 不能联通了,那么在2^p-1 ~ 2^p 范围内二分

这样时间复杂度是mlogm的

 

 

 如果小兵的血量是1 2 3 4 5 ……

那么显然我们可以补到所有的兵

如果有相同血量的兵,那么只能补到其中的1个兵

所以我们要尽可能的把给出的兵的血量变成1 2 3 4 5 ……

一种可行的方案是 重复血量的兵 强制消耗代价使他 变成 血量更小 但血量不重复的兵

可以用栈记录之前没有的血量,每遇到一个重复的就用栈中的一个血量

例:1 2 4 4

扫到4的时候,之前没有血量3,3入栈

后面还是1个4,就让3出栈,即消耗代价 使4变成3

令c[i]=j 记录消耗代价后血量为i的兵实际上是原血量为j的兵

 

然后DP

 

dp[i][j] 表示到血量为i的兵,省下了j刀的最大补兵数

省下了j刀:就是先把兵的血量看成1,2,3,然后考虑每个兵砍或不砍。如果不砍,就可以省下一刀给以后用 

 

 所以如果不砍,状态转移为 dp[i][j]=dp[i-1][j-1]

如果砍的话,砍血量为i的兵要加上之前强制消耗的代价,所以dp[i][j]=dp[i-1][j+c[i]-i]+1

 

老鹿的攻击怎么体现的呢?

因为每次尽可能的让兵的血量变为1,2,3……

自己砍掉一个血量为1的兵,后面再老鹿的攻击下又产生了一个血量为1的兵

但实际DP时

从1开始枚举血量

血量为2时,实际血量为1,相当于 提高了 兵死亡时的血量

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

#define N 1001

int f[N][N];

int a[N],c[N];

int cnt[N];

int st[N],top;

int main()
{
    freopen("cs.in","r",stdin);
    freopen("cs.out","w",stdout);
    int T,n,mx;
    scanf("%d",&T);
    while(T--)
    {
        memset(f,0,sizeof(f));
        memset(cnt,0,sizeof(cnt));
        memset(c,0,sizeof(c));
        top=mx=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]),mx=max(mx,a[i]),cnt[a[i]]++;
        sort(a+1,a+n+1);
        for(int i=1;i<=mx;i++)
        if(!cnt[i]) st[++top]=i;
        else
        {
            while(cnt[i]>1 && top ) c[st[top--]]=i,--cnt[i];
            c[i]=i;
        }
        int ans=0;
        for(int i=1;i<=mx;++i)
            for(int j=0;j<i;++j)
            {
                if(j) f[i][j]=f[i-1][j-1];
                if(c[i] && j+c[i]-i<i) f[i][j]=max(f[i][j],f[i-1][j+c[i]-i]+1);
                ans=max(ans,f[i][j]);
            } 
        cout<<ans<<'\n'; 
    } 
}
View Code

 

转载于:https://www.cnblogs.com/TheRoadToTheGold/p/7761063.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值