博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

【考试总结】NOIP模拟 test11-1

Tips:

中文题目名称

奇数码问题

树洞

まんふは函数

英文题目与文件名

digital

holes

function

输入文件名

digital.in

holes.in

function.in

输出文件名

digital.out

holes.out

function.out

每个测试点时限

1

1秒

1

内存限制

256 MB

256 MB

256 MB

测试点数目

10

10

10

每个测试点分值

10

10

10

结果比较方式

全文比较(过滤行末空格及文末回车)

全文比较(过滤行末空格及文末回车)

全文比较(过滤行末空格及文末回车)

题目类型

传统

传统

传统

 

Problem

 

digital(奇数码问题)

Description

你一定玩过八数码游戏,它实际上是在一个33的网格中进行的,1个空格和1~8这8个数字恰好不重不漏地分布在这33的网格中。

例如:

5 2 8

1 3 _

4 6 7

在游戏过程中,可以把空格与其上、下、左、右四个方向之一的数字交换(如果存在)。 例如在上例中,空格可与左、上、下面的数字交换,分别变成:

5 28

5 2 _

5 2 8

 

1 _ 3

1 3 8

1 3 7

 

4 6 7

4 6 7

4 6 _

 

奇数码游戏是它的一个扩展,在一个nn的网格中进行,其中n为奇数,1个空格和1~nn-1这nn-1个数恰好不重不漏地分布在nn的网格中。

空格移动的规则与八数码游戏相同,实际上,八数码就是一个n=3的奇数码游戏。

现在给定两个奇数码游戏的局面,请判断是否存在一种移动空格的方式,使得其中一个局面可以变化到另一个局面。

Input Format

多组数据,对于每组数据:

1行一个奇整数n。

接下来n行每行n个整数,表示第一个局面。

接下来n行每行n个整数,表示第二个局面。

局面中每个整数都是0~n*n-1之一,其中用0代表空格,其余数值与奇数码游戏中的意义相同,保证这些整数的分布不重不漏。

Output Format

对于每组数据,若两个局面可达,输出TAK,否则输出NIE。

Sample Input

3

1 2 3

0 4 6

7 5 8

1 2 3

4 5 6

7 8 0

1

0

0

Sample Output

TAK

TAK

Hint

数据范围与约定

对于30%的数据,1<=n<=3;

对于60%的数据,1<=n<=50;

对于100%的数据,1<=n<=500,n为奇数,每个测试点不超过10组。

Solution

把矩阵中的所有数依次排成一行,求逆序对数。若起始状态逆序对数与目标状态同奇偶,则可达,否则不可达。

奇数码中的任何移动均不改变上述逆序对数的奇偶性。

那么为什么用逆序对判断呢(雾)。

#include<cmath>
#include<cstdio>
#include<cstring>

int n;
int a[250007];
int c[250007];
int res;

void merge_sort(int l,int r)
{
    if (l>=r) return;
    int mid=(l+r)>>1;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    int i=l,j=mid+1,k=l;
    while (i<=mid&&j<=r)
    {
        if (a[i]<a[j]) c[k++]=a[i++];
        else 
        {
            c[k++]=a[j++];
            res=res+mid-i+1;
        }
    }
    while (i<=mid) c[k++]=a[i++];
    while (j<=r) c[k++]=a[j++];
    for (int i=l;i<=r;i++)    a[i]=c[i];
}

int main()
{
    freopen("a.in","r",stdin);
    while (~scanf("%d",&n))
    {    
        memset(a,0,sizeof a);
        int tot1=0,tot2=0;res=0;
        for (int i=1;i<=n*n;i++) 
        {
            int x;
            scanf("%d",&x);
            if (x) a[++tot1]=x;
        }
        merge_sort(1,tot1);
        tot1=res;res=0;
        for (int i=1;i<=n*n;i++) 
        {
            int x;
            scanf("%d",&x);
            if (x) a[++tot2]=x;
        }
        merge_sort(1,tot2);
        tot2=res;
        res=std::abs(tot1-tot2);
        if (res&1) printf("NIE\n");
        else printf("TAK\n");
    }
}

 

 

 

holes(树洞)

Description

在一片栖息地上有N棵树,每棵树下住着一只兔子,有M条路径连接这些树。更特殊地是,只有一棵树有3条或更多的路径与它相连,其它的树只有1条或2条路径与其相连。换句话讲,这些树和树之间的路径构成一张N个点、M条边的无向连通图,而度数大于2的点至多有1个。

近年以来,栖息地频繁收到人类的侵扰。兔子们联合起来召开了一场会议,决定在其中K棵树上建造树洞。当危险来临时,每只兔子均会同时前往距离它最近的树洞躲避,路程中花费的时间在数值上等于距离。为了在最短的时间内让所有兔子脱离危险,请你安排一种建造树洞的方式,使最后一只到达树洞的兔子所花费的时间尽量少。

Input Format

第一行有3个整数N,M,K,分别表示树(兔子)的个数、路径数、计划建造的树洞数。

接下来M行每行三个整数x,y,表示第x棵树和第y棵树之间有一条路径相连。1<=x,y<=N,x≠y,任意两棵树之间至多只有1条路径。

Output Format

一个整数,表示在最优方案下,最后一只到达树洞的兔子所花费的时间。

sample input

5 5 2

1 2

2 3

3 1

1 4

4 5

sample output

1

Hint

数据范围与约定

对于20%的数据,1 ≤  n ≤ 10。

对于另外30%的数据,每棵树至多与2条路径相连。

对于另外30%的数据,保证存在一种最优解,使与3条或更多路径相连的树上一定建造了树洞。 对于100%的数据,1 ≤ n ≤ 2000,n-1<=m<=n*(n-1)/2。

Solution

求最大值最小,而且答案满足单调,很显然可以用二分
如何验证?
首先考虑一条链的情况,答案必然是(n-k)/k;
而另外存在一个特殊点有三条及以上的边的情况
我们先枚举一个点,可以将特殊点覆盖,覆盖后,
原来的图将断成若干条链,然后重复之前链的做法统计答案
是否超过k,来判断二分的答案是否正确

#include<cstdio>
#include<cstring>

int n,m,k,rt,deep,mi;
int deg[2005],dis[2005];
bool vis[2005],first[2005];
int head[2005],num;
struct edge
{
    int next,to;
}e[4000005];

void add(int x,int y)
{
    e[++num].next=head[x];
    e[num].to=y;
    head[x]=num;
}

void dfs(int x,int len)
{
    deep+=1;
    vis[x]=1;
    if (!len) return;
    for (int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if (!vis[v]) 
        {
            dis[v]=dis[x]+1;
            dfs(v,len-1);
        }
    }
}

bool check(int x)
{
    memset(vis,0,sizeof vis);
    dfs(rt,x);
    memcpy(first,vis,sizeof vis);
    for (int i=1;i<=n;i++)
    {
        if (first[i])
        {
            int res=0;
            memset(vis,0,sizeof vis);
            dfs(i,x);
            for (int j=1;j<=n&&res<k;j++)
            if (!vis[j])
            {
                deep=0;
                dfs(j,n);
                res+=(deep+2*x)/(2*x+1);
            }
            if (res<k) return 1;
        }
    }
    return 0;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        deg[x]++,deg[y]++;
        add(x,y),add(y,x);
    }
    for (int i=1;i<=n;i++)
        if (deg[i]>2) {rt=i;break;}
    if (!rt)
    {
        printf("%d",(n+k-1)/k/2);
        return 0;
    }
    int l=1,r=n-1,ans=n;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d",ans);
}

 

function(まんふは函数

Description

n个正整数A[1],A[2]…A[n],满足A[i]>=A[i+1]。

它们可以产生n个正整数B[1], B[2] …B[n],其中B[i]=∑j=inA[j]

まんふは函数(マンフハ函数f:(Z,Z)→Z定义为

 

试求f(n,1)

Input Format

输入包含多组数据不超过10

每组数据的第一行是一个正整数,第二行n个正整数A[i]

Output Format

对于每组数据,输出一个整数表示f(n,1)

sample input

3

1 1 1

5

28 26 25 24 1

10

996 901 413 331 259 241 226 209 139 49

sample output

5

233

11037

Hint

对于第一组数据

f(1,1)=0

f(1,2)=f(1,1)+3=3

f(1,3)=f(1,2)+3=6

f(2,1)=min(f(2,1)+2,f(1,2))=3

f(2,2)=min(f(2,1)+2,f(1,3))=5

f(2,3)=f(2,2)+2=7

f(3,1)=min(f(3,1)+1,f(2,2))=5

 

对于30%的数据,满足1n20

对于60% 的数据,满足1≤n≤1000。

对于 100% 的数据,满足1≤n1000001A[i]10000

Solution

题目中要求min{f[n][1]}的值
可以理解成已经合并了n个点,形成1个树的最小值
每次有两个操作一个是将一个点与一棵子树合并,一个是将两棵子树合并
最后要求的是最小值,所以每次合并的值都要最小,
用单调队列去维护这个最小值即可。

#include<cstdio>
#include<cstring>

int n,x,y,lenu;
long long node[100005],unio[100005];

long long top()
{
    if (x<=n&&(y>lenu||node[x]<unio[y])) return node[x++];
    return unio[y++];
}

int main()
{
    while (~scanf("%d",&n))
    {
        long long ans=0;
        x=1,y=1,lenu=0;
        memset(node,0,sizeof node);
        memset(unio,0,sizeof unio);
        int tot=n;
        for (int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            node[tot--]=x;
        }
        for (int i=1;i<n;i++)
        {
            unio[lenu+1]=top()+top();
            ans+=unio[++lenu];
        }
        printf("%lld\n",ans);
    }
}

 

posted @ 2017-11-02 11:43  JadeDark  阅读(487)  评论(0编辑  收藏  举报