【NOIP2017】SummerTraining0706

又来补之前的考试了……这套题目订正起来不是很难,但现场……
10+0+40=50,rank22
T1不会求最大的平均长度,原来是二分+判圈,做题太少啊
T2是一道不难的DP,从头开始推很简单,写了一篇专门的blog
T3数据结构题,唯一看出算法的,卡在了一些小地方……
%xqj AC T1 T2 %lzw AC T3

看题解或代码的看目录,有索引。

T1

问题 A: 单词接龙1
时间限制: 5 Sec 内存限制: 256 MB Special Judge
题目描述
Bsny从字典挑出N个单词,并设计了接龙游戏,只要一个单词的最后两个字母和另一个单词的前两个字母相同,那么这两个单词就可以有序的连接起来。

Bsny想要知道在所给的所有单词中能否按照上述方式接龙组成一个单词环(可能是多个),若能,求所有环的环中单词平均长度最大值。

输入
第一行一个整数N,表示单词数量。

接下来N行,每行一个字符串,仅包含小写字母。

输出
若能组成单词环,输出环中单词的最大平均长度,结果保留2位小数;否则输出”No solution.”(不包括双引号)。精度误差在0.01都算正确。

样例输入
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein

样例输出
21.67
提示

20%的数据:n≤20;

70%的数据:n≤1000;

100%的数据:n≤100000,每个单词长度不超过1000。输入数据比较大,C/C++的同学用scanf输入。

Solution

100分做法:二分+SPFA判环
单词看成边,两个字母组合看成点。这样,庞大的输入数据就变成了一个点数至多为262的有向图。
接下来就是在有向图上找平均边长最大的环。可以用二分答案。二分一个len,将所有边权都减去len,若图中存在正权环,那么len可以增大,否则就减小。直到找到答案。
判断是否存在环,用SPFA,检查某个点入队次数,超过总点数即存在环。
PS:
1.判正权圈用最长路,或者变负数最短路。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXM=101000,MAXN=26*26+10;
int Head[MAXN],Next[MAXM],To[MAXM],cnt[MAXN];
double Key[MAXM],dis[MAXN];
bool inq[MAXN];
char str[1010];
int n,tot;

int getnum(int x,int y) { return (x-97)*26+y-97; }
void add(int x,int y,int w)
{
    tot++;
    Next[tot]=Head[x];
    Head[x]=tot;
    To[tot]=y;
    Key[tot]=(double)w; 
}
void ReadInfo()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        int m,x,y;
        scanf("%s",str+1);
        m=strlen(str+1);
        x=getnum(str[1],str[2]);
        y=getnum(str[m-1],str[m]);
        add(x,y,m); 
    }
}
bool spfa(int s,double avg)//true means there exist a positive weight circle;
{
    memset(inq,false,sizeof(inq));
    memset(dis,0xc2,sizeof(dis));
    queue<int>Q;
    dis[s]=0; inq[s]=true; cnt[s]=1;
    Q.push(s);
    while (!Q.empty())
    {
        int u=Q.front(); 
        Q.pop(); inq[u]=false;
        for (int i=Head[u];i;i=Next[i])
        {
            int v=To[i];
            double w=Key[i]-avg;
            if (dis[u]+w>dis[v])
            {
                dis[v]=dis[u]+w;
                if (!inq[v])
                {
                    inq[v]=true; Q.push(v); cnt[v]++;
                    if (cnt[v]==MAXN) return true;
                }
            }
        }
    }
    return false;   
}
bool positive_circle(double avg)//true means there exist a positive weight circle;
{
    memset(cnt,0,sizeof(cnt));
    for (int i=1;i<MAXN;i++)
        if (!cnt[i])
            if (spfa(i,avg)) return true;
    return false;
}
void solve()
{
    double x=0,y=1000,mid;
    while (y-x>1e-2)
    {
        mid=(x+y)/2;
        if (positive_circle(mid)) x=mid;
        else y=mid;
    }   
    if (x<0.1) printf("No solution\n");
    else printf("%.2lf\n",x);
}

int main()
{
    ReadInfo();
    solve();    
    return 0;
}

T2

http://blog.csdn.net/lhq_er/article/details/74905871

T3

问题 C: 分组
时间限制: 1 Sec 内存限制: 256 MB
题目描述
Bsny所在的精灵社区有n个居民,每个居民有一定的地位和年龄,ri表示第i个人的地位,ai表示第i个人的年龄。

最近社区里要举行活动,要求几个人分成一个小组,小组中必须要有一个队长,要成为队长有这样的条件:

1、队长在小组中的地位应该是最高的(可以并列第一);

2、小组中其他成员的年龄和队长的年龄差距不能超过K。

有些人想和自己亲密的人组在同一个小组,同时希望所在的小组人越多越好。比如x和y想在同一个小组,同时希望它们所在的小组人越多越好,当然,它们也必须选一个符合上述要求的队长,那么问你,要同时包含x和y的小组,最多可以组多少人?

输入
第一行两个整数n和K;

接下来一行输入n个整数:r1, r2, …, rn

接下来一行输入n个整数:a1, a2, …, an

接下来输入Q表示有Q个询问;

接下来Q行每行输入x, y,表示询问:当x和y组在同一个小组,它们小组最多可以有多少人(x和y也有可能被选为队长,只要它们符合条件)。

输出
对于每个询问,输出相应的答案,每个答案占一行。

当x和y无法在同一组时,输出-1(比如x的年龄是1, y的年龄是100,K=1,无论谁当队长,x和y两者中,总会有人跟队长的年龄差距超过K,那么输出-1)。

样例输入
5 1
1 5 4 1 2
4 4 3 2 2
4
5 3
2 3
2 5
4 1

样例输出
4
3
-1
4

提示

【样例解释】

询问1:当第5个人和第3个人想在一组时,小组成员可以有{1, 3, 4, 5},选择3当队长,而2不可以加入,因为2加入的话,5和2的年龄差距为2,超过K=1了;

询问2:当第2个人和第3个人想在一组时,可以选择{1, 2, 3};

询问3:当2和5想在一起时,无法满足要求;

询问4:当4和1想在一起时,可以选择{1, 3, 4, 5};

【数据规模】

20%的数据:2≤n≤100,0≤ k≤100,1≤ ri, ai ≤100,1≤ q≤ 100;

40%的数据:2≤ n≤1000,0≤ k≤ 1000,1≤ ri, ai ≤ 1000,1≤ q≤ 1000;

60%的数据:2≤ n≤ 104,0≤ k≤ 109,1≤ ri, ai ≤ 109, 1≤ q≤ 104;

100%的数据:2≤ n≤ 105,0≤ k≤ 109,1≤ ri, ai ≤ 109,1≤ q≤ 105,1≤ x, y≤ n, x≠y。

Solution

题解写的比较详细,贴了
100分做法:离散化+树状数组+离线处理+线段树
首先对于每个人,我们可以预处理如果他是队长的话,最多可以有少人组队:
这里需要对r进行从小到大排序,排序完后,我们可以从小到大遍历每个人,利用树状数组统计[ai-k, ai+k]的人数,即为第i个人作为队长最大组队人数
这里要对相同r的时候特殊处理一下
然后,对于一组询问x,y, 我们可以计算出能包含x,y的组,队长的a和r的限制条件,即a的范围为[max(x.a-k, y.a+k), min(x.a+k, y.a+k)], r的范围为r>=max(x.r, y.r)
我们可以在符合a,r范围的所有人中寻找最大值
但考虑到询问比较大,我们可以采取离线的方式处理询问:
用rmin表示满足询问r的最小值,即max(x.r, y.r),对询问根据rmin从大到小排序,接下来的处理就是求[max(x.a-k, y.a+k), min(x.a+k, y.a+k)]范围中最大值是多少,这个可以用线段树来维护和求解
如果max(x.a-k, y.a+k)> min(x.a+k, y.a+k)或者找不到符合r的人,那么答案为-1
因此,总的复杂度为O(nlogn)

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
int rs[MAXN],as[MAXN],c[MAXN],lead[MAXN],tree[MAXN<<2];
int n,k,m;
struct node
{
    int a,r;
}p[MAXN];
struct node2
{
    int x,y,z,id,ans;
}ques[MAXN];
bool cmp(node a,node b){return a.r<b.r;}
bool cmp2(node2 a,node2 b){return a.z>b.z;}
bool cmp3(node2 a,node2 b){return a.id<b.id;}


int read()
{
    int data=0; char ch=0;
    while (ch<'0' || ch>'9') ch=getchar();
    while (ch>='0' && ch<='9') 
    {
        data=data*10+ch-48;
        ch=getchar();
    }
    return data;    
}
void write(int x)
{
    if (x<0) { x=-x; putchar('-'); }
    if (x>9) write(x/10);
    putchar(x%10+48);   
}
void add(int x,int val)
{
    for (int i=x;i<=MAXN;i+=i&(-i))
        c[i]+=val;
}
int sum(int x)
{
    int ans=0;
    for (int i=x;i>0;i-=i&(-i))
        ans+=c[i];
    return ans;
}
int sum(int x,int y)
{
    return sum(y)-sum(x-1);
}
void push_up(int v)
{
    tree[v]=max(tree[v<<1],tree[v<<1|1]);
}
void build(int v,int l,int r)
{
    if (l==r) {tree[v]=-1;return;}
    int mid=(l+r)>>1;
    build(v<<1,l,mid);
    build(v<<1|1,mid+1,r);
    push_up(v);
}
void modify(int v,int l,int r,int x,int val)
{
    if (l==r) {tree[v]=max(tree[v],val); return;}
    int mid=(l+r)>>1;
    if (x<=mid) modify(v<<1,l,mid,x,val);
    else        modify(v<<1|1,mid+1,r,x,val);
    push_up(v);
}
int query(int v,int l,int r,int x,int y)
{
    if (y<l || r<x) return -1;
    if (x<=l && r<=y) return tree[v];
    int mid=(l+r)>>1;
    return max(query(v<<1,l,mid,x,y),query(v<<1|1,mid+1,r,x,y));
}
void solve()
{
    //计算lead数组,lead[i]表示i能领导的人数;
    for (int i=1;i<=n;i++)
    {
        int x=lower_bound(as+1,as+1+as[0],p[i].a)-as;
        add(x,1);
        while (i+1<=n && p[i+1].r==p[i].r) 
            i++,x=lower_bound(as+1,as+1+as[0],p[i].a)-as,add(x,1);
        for (int j=i;j==i || p[j].r==p[j+1].r;j--)
        {
            int x1=lower_bound(as+1,as+1+as[0],p[j].a-k)-as;
            int x2=upper_bound(as+1,as+1+as[0],p[j].a+k)-as-1;
            lead[j]=sum(x1,x2);
        }
    }
    //处理各个询问,已经预处理完毕;
    build(1,1,as[0]);
    int j=n;
    for (int i=1;i<=m;i++)
    {
        int x=lower_bound(as+1,as+1+as[0],ques[i].x)-as;
        int y=upper_bound(as+1,as+1+as[0],ques[i].y)-as-1;
        if (x>y) {ques[i].ans=-1; continue;}
        int z=ques[i].z;
        while (j>0 && p[j].r>=z) 
        {
            int aa=lower_bound(as+1,as+1+as[0],p[j].a)-as;
            modify(1,1,as[0],aa,lead[j]);
            j--;
        }
        ques[i].ans=query(1,1,as[0],x,y);
    }
    sort(ques+1,ques+1+m,cmp3);
    for (int i=1;i<=m;i++)
        {write(ques[i].ans);putchar('\n');}  
}

void ReadInfo()
{
    n=read(); k=read();
    //读入r数组并排序去重入rs数组,并对r数组离散化;
    for (int i=1;i<=n;i++)
    {
        p[i].r=read();
        rs[++rs[0]]=p[i].r;
    }
    sort(rs+1,rs+1+rs[0]);
    rs[0]=unique(rs+1,rs+1+rs[0])-rs-1;
    for (int i=1;i<=n;i++)
        p[i].r=lower_bound(rs+1,rs+1+rs[0],p[i].r)-rs;
    //读入a数组并排序去重入as数组;
    for (int i=1;i<=n;i++)
    {
        p[i].a=read();
        as[++as[0]]=p[i].a;
    }
    sort(as+1,as+1+as[0]);
    as[0]=unique(as+1,as+1+as[0])-as-1;
    //读入询问,并按地位大值排序;
    m=read();
    for (int i=1;i<=m;i++)
    {
        int x,y;
        x=read(); y=read();
        ques[i].x=max(p[x].a-k,p[y].a-k);
        ques[i].y=min(p[x].a+k,p[y].a+k);
        ques[i].z=max(p[x].r,p[y].r);
        ques[i].id=i;
    }
    sort(ques+1,ques+1+m,cmp2);
    //为了确保询问指向的人的正确性,后排序p数组;
    sort(p+1,p+1+n,cmp);
}
int main()
{
    ReadInfo();
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值