【XJOI】NOIP2016提高组冲剌题1

老司机就是老司机……这题真TM好
丢你连接

T1

题目大意

对于每一行,可以选择若干个连续的数,要求平均值最小
NOIP不考double!!!!


最大化平均值?
二分!
单调性?
唔……
不知道!
好吧我们继续……

设答案为x
则有 ni=1sum[i][h[i]]ni=1[h[i]]x ,其中,sum[i][j] = 第i行前j个元素的和,h[i]表示第i行选了多少个元素
//注意,现在h[i]是未知的
两边同乘 ni=1[h[i]]
ni=1sum[i][h[i]]xni=1h[i]
ni=1sum[i][h[i]]ni=1(h[i]x)
移项,得

i=1n(sum[i][h[i]](h[i]x))0

现在我们将问题转化成了求子集使得上式成立
我们发现如果 上式成立则说明答案x可行
对于每一个 i 我们贪心的选择最大的sum[i][h[i]]h[i]x这样可以尽可能的使Σ变大
恩恩

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int MAXN = 100000 + 5;
int n,m;
vector <double> summer[MAXN];
bool can(double x)
{
    double sum = 0;
    for(int i = 1;i <= n;i ++)
    {
        double tmp = -2147483647;
        for(int j = 0;j < m;j ++)
            tmp = max(summer[i][j] - x * (j + 1),tmp);
        sum += tmp;
    }
    return sum >= 0;
}
double diver(double l,double r)
{
    double ans = 0;
    for(int i = 1;i <= 100;i ++)
    {
        double mid = (l + r) / 2;
        if(can(mid))
            ans = l = mid;
        else
            r = mid;
    }
    return ans;
}
double x;
int main()
{
    scanf("%d %d",&n,&m);
    double r = 0;
    for(int i = 1;i <= n;i ++)
    {
        double s = 0;
        for(int j = 1;j <= m;j ++)
        {
            scanf("%lf",&x);
            summer[i].push_back(s + x);
            s += x;
            r = max(r,x);
        }
    }
    printf("%.4lf",diver(0,r));
    return 0;
}

T2

求一个子集,这个子集中的任意两种数字连成的边都有相交


将读入的两个序列分别叫做 up[]down[]
两条线端有交叉的充要条件是 up[i]up[j]down[i]down[j]
由于线段只会在标号相同的两点之间出现,因此 down[i]down[j]
恩恩
我们定义map[i]表示i在上面的序列(up[])中出 现的位置,并且
up[i] = map[up[i]],down[i] = map[down[i]]
那么上面的序列就变成了 1,2,3,4,5...n
下面的序列就变成了….谁知道呢
因为我们只需要求有多少条线段相交,那么我们改变编码方式不会导致WA
但是这样做之后,第一个条件( up[i]up[j] )就已经满足了
那么我只需要保证 down[i]down[j]
怎么做?
保证i递增并且num[i]递减……
这是什么?
最长下降子序列!
恩恩
求吧

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 100000 + 5;
int n;
int g[MAXN];
int where(int x)
{
    return lower_bound(g + 1,g + n + 1,x) - g;
}
int num[MAXN];
int dpdpd()
{
    memset(g,0x3f,sizeof(g));
    int len = 0;
    for(int i = 1;i <= n;i ++)
    {
        int k = where(num[i]);
        g[k] = min(g[k],num[i]);
        len = max(len,k);
    }
    return len;
}
int map[MAXN];
int x;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
    {
        scanf("%d",&x);
        map[x] = i;
    }
    for(int i = 1;i <= n;i ++)
    {
        scanf("%d",&x);
        num[i] = -map[x];
    }
    printf("%d\n",dpdpd());
    return 0;
}

T3

题目大意

这里写图片描述


做题的时候没有发现对于第一种操作,人进入的顺序是一定的,是修改后的dfs序
QAQ
用了巧妙的存图方式
好题
第一问可以将节点以及dfs序丢到堆里
第二问可以倍增lca
不说太多了……放到错题本里

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int MAXN = 100000 + 5;
const int MAX_LOG = 30 + 5;
const int LOG = 20;
vector <int> G[MAXN];
int dfn[MAXN],dfs_clock;
int fa[MAXN][MAX_LOG];
int n,m;
void build(int f,int t)
{
    G[f].push_back(t);
    return;
}
void dfs(int x,int f)
{
    fa[x][0] = f;
    for(int i = 0;i < G[x].size();i ++)
        if(G[x][i] != f)
            dfs(G[x][i],x);
    dfn[x] = ++ dfs_clock;
    return;
}
bool inque[MAXN];
void make_fa()
{
    for(int j = 1;j <= LOG;j ++)
        for(int i = 1;i <= n;i ++)
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
    return;
}
int ask(int &x)
{
    int ans = 0;
    for(int j = LOG;j >= 0;j --)
        if(!inque[fa[x][j]])
            x = fa[x][j],ans += 1 << j;
    return ans;
}
struct dot
{
    int u;
    bool operator < (const dot &b)const
    {
        return dfn[u] > dfn[b.u];
    }
};
int f,t;
priority_queue <dot> q;
int Q,x;
int main()
{
    scanf("%d %d",&n,&m);
    for(int i = 1;i < n;i ++)
    {
        scanf("%d %d",&f,&t);
        build(f,t);
        build(t,f);
    }
    for(int i = 1;i <= n;i ++)
        sort(G[i].begin(),G[i].end());
    dfs(1,0);
    make_fa();
    for(int i = 1;i <= n;i ++)
        q.push((dot){i}),inque[i] = true;
    inque[0] = true;
    while(m --)
    {
        scanf("%d %d",&Q,&x);
        int ans;
        switch(Q)
        {
            case 1:
                for(int i = 1;i <= x;i ++)
                {
                    ans = q.top().u;
                    q.pop();
                    inque[ans] = false;
                }
                break;
            case 2:
                ans = ask(x);
                inque[x] = true;
                q.push((dot){x});
                break;
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值