【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;
}
發佈了78 篇原創文章 · 獲贊 17 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章