noip 2013总结

转圈游戏

题目

n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏。按照顺时针方向给 n 个位置编号,从0 到 n-1。最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置,……,依此类推。游戏规则如下:每一轮第 0 号位置上的小伙伴顺时针走到第 m 号位置,第 1 号位置小伙伴走到第 m+1 号位置,……,依此类推,第n − m号位置上的小伙伴走到第 0 号位置,第n-m+1 号位置上的小伙伴走到第 1 号位置,……,第 n-1 号位置上的小伙伴顺时针走到第m-1 号位置。

现在,一共进行了 10^k轮,请问 x 号小伙伴最后走到了第几号位置。

输入输出格式

输入格式:
输入文件名为 circle.in。

输入共 1 行,包含 4 个整数 n、m、k、x,每两个整数之间用一个空格隔开。

输出格式:
输出文件名为 circle.out。

输出共 1 行,包含 1 个整数,表示 10^k 轮后 x 号小伙伴所在的位置编号。

样例

样例输入

10 3 4 5

样例输出

5

说明

数据范围
对于 30%的数据,0 < k < 7;
对于 80%的数据,0 < k < 10^7;
对于 100%的数据,1 <n < 1,000,000,0 < m < n,1 ≤ x ≤ n,0 < k < 10^9

思路

  1. 先通过草稿手动模拟,可以得到规律,最后的位置为(x+m*10^k)%n
  2. 很明显,算m*10^k需要用到快速幂
  3. 数据范围:为了防止爆long long要不断%n

代码

#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
long long n;
long long pow(int root, int time) {
    int  ans=1;
    while(time) {
        if(time & 1) ans=(root*ans)%n;
        root=(root*root)%n;
        time>>=1;
    }
    return ans%n;
}
int main() {
    long long m,k,x;
    long long t,r;
    cin>>n>>m>>k>>x;
    r=0;
    t=pow(10,k);
    t*=m;
    t%=n;
    r=(x+t)%n;
    cout<<r<<endl;
    return 0;
}

火柴排队

题目

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为: ∑(ai-bi)^2
其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。

输入输出

输入:
输入文件为 match.in
共三行,第一行包含一个整数 n,表示每盒中火柴的数目。
第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。
第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。

输出
输出文件为 match.out
输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果

思路

  1. 先来证明一个公式:
    若a1>a2且b1>b2,则有(a1-b1)^2 +(a2-b2)^2 < (a2-b1)^2 + (a1-b2)^2
    当然这个公式很容易证,拆开就好了
  2. 然后运用这个公式,发现为保证火柴距离最小,两列火柴对应的两根火柴在各列中高度的排名应该相同
  3. 再来定义一个r数组,使得r[a[i].num]=b[i].num,则可以很惊奇地发现,交换次数即为r数组中逆序对的个数,此题得解

现在考虑求逆序对

用归并排序求:
实际上归并排序的交换次数就是这个数组的逆序对个数,为什么呢?

  1. 归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
  2. 在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
  3. 前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并排序中的合并过程中计算逆序数.

用树状数组求:

  1. 每输入一个b[i],就用a[b[i]+1]++去标记总共输入多少次了(因为输入的数据是0~n-1,所以输入的b[i]就相当于a[i]的下标i,而且有0,树状数组无法对下标为0进行操作,所以要a[b[i]+1]);
  2. 对于一个b[i],要想查询它前面有多少大于它的,只需将a[b[i]+1]到a[n]加起来,也就是求一段数组的和,那么树状数组就上场加速了

代码

归并排序

#include<cstdio>
#include<algorithm>
using namespace std;
typedef struct n{
    int num,ord;
}node;
node first_team[100010],second_team[100010];
int a[100010],b[100010],ans;
int compare(node x,node y)
{
    return x.num<y.num;
}
void Merge(int l,int r)
{
    if(l>=r) return ;
    int mid=(l+r)/2;
    Merge(l,mid);
    Merge(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
    {
        if(a[i]>a[j])
        {
            b[k++]=a[j++];
            ans+=mid-i+1;
            ans%=99999997;
        }
        else b[k++]=a[i++];
    }
    while(i<=mid) b[k++]=a[i++];
    while(j<=r) b[k++]=a[j++];
    for(int i=l;i<=r;i++)
        a[i]=b[i];
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&first_team[i].num);
        first_team[i].ord=i;
    }
    for(int i=1;i<=n;i++)`
    {
        scanf("%d",&second_team[i].num);
        second_team[i].ord=i;
    }
    sort(first_team+1,first_team+n+1,compare);
    sort(second_team+1,second_team+n+1,compare);
    for(int i=1;i<=n;i++)
        a[first_team[i].ord]=second_team[i].ord;
    Merge(1,n);
    printf("%d",ans);
    return 0;
}

树状数组

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100010;
const int maxm = 99999997;
struct MyStruct
{
    int data;
    int loc;
}a[maxn],b[maxn];
int e[maxn], n, c[maxn];
int inline readint()
{
    int x = 0;
    char c = getchar();
    while (c<'0' || c>'9') c = getchar();
    while (c >= '0'&&c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x;
}
int lowbit(int x)
{
    return x&-x;//树状数组实现 
}
void add(int x,int t)
{
    while (x <= n)
    {
        e[x] += t;
        e[x] %= maxm;
        x += lowbit(x);//每次往后加,可以改变后面对应的和 
    }
}
int sum(int x)
{
    int s = 0;
    while(x)
    {
        s += e[x];
        s %= maxm;
        x -= lowbit(x);//得到所求的和 
    }
    return s;
}
bool cmp(MyStruct x, MyStruct y)
{
    return x.data < y.data;
}
int main()
{
    n = readint();
    for (int i = 1; i <= n; i++)
    {
        a[i].data = readint();
        a[i].loc = i;//记录位置 
    }
    for (int i = 1; i <= n; i++)
    {
        b[i].data = readint();
        b[i].loc = i;
    }
    sort(a + 1, a + n + 1, cmp);
    sort(b + 1, b + n + 1, cmp);
    for (int i = 1; i <= n; i++)
    {
        c[a[i].loc] = b[i].loc;//离散优化 
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        add(c[i], 1);//离散优化后大小就是正确顺序的位置 
        ans += i - sum(c[i]);//当前位置,减去之前比他大的数的个数  
        ans %= maxm;
    }
    printf("%d", ans);
    return 0;
}

积木大赛

题目

春春幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第i块积木的最终高度需要是hi。

在搭建开始之前,没有任何积木(可以看成n块高度为 0 的积木)。接下来每次操作,小朋友们可以选择一段连续区间[l, r],然后将第第 L 块到第 R 块之间(含第 L 块和第 R 块)所有积木的高度分别增加1。

小 M 是个聪明的小朋友,她很快想出了建造大厦的最佳策略,使得建造所需的操作次数最少。但她不是一个勤于动手的孩子,所以想请你帮忙实现这个策略,并求出最少的操作次数。

输入输出格式

输入格式:
输入文件为 block.in
输入包含两行,第一行包含一个整数n,表示大厦的宽度。
第二行包含n个整数,第i个整数为hi 。

输出格式:
输出文件为 block.out
仅一行,即建造所需的最少操作数。

输入输出样例

样例输入

5
2 3 4 1 2

样例输出

5

思路

其实就是简单的贪心,观察样例;

a[1]=2 所以位置1一定被操作了两次 ans+=2
a[2]-a[1]=1 位置2比位置1多操作一次 ans+=1
但a[i]<a[i-1]时,跳过。因为对于a[i]<a[i-1]的情况,a[i]和a[i-1]一定不在一次操作内
以此类推,就得到最后的答案

代码

/*很简短*/
#include<iostream>
#include<cstdio>
#define MAXX 100000+5
using namespace std;
int a[MAXX],n;
long long ans=0;
int main() {
    scanf("%d",&n);
    for (int i=1; i<=n; i++) {
        scanf("%d",&a[i]);
        if (a[i]>a[i-1]) ans+=a[i]-a[i-1];
    }
    printf("%lld",ans);
    return 0;
}

货车运输

题目描述

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入输出

输入格式:
输入文件名为 truck.in。

输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。

输出格式:
输出文件名为 truck.out。

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

输入输出样例

输入样例:

4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

输出样例#1:

3
-1
3

说明

数据范围
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。

思路

  • 求一条路径的最小边的最大值,符合该条件的路径一定在最大生成树上
  • 在最大生成树上跑LCA倍增。即求起点到LCA上最小边和终点到LCA上最小边的最小值

代码

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;
const int inf = 100001;
const int maxn = 10010;
const int maxm = 50050;
int n, m, q, tot;
int st[maxn], vis[maxn], deep[maxn], fa[maxn][17], g[maxn][17], f[maxn];

struct node{
    int v, w, next;
} edge[maxm];

struct node1{
    int u, v, w;
} a[maxm];

bool cmp(node1 x, node1 y){
    return x.w > y.w;
}

void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].next = st[x];
    st[x] = tot;
}

void init(){
    for(int i = 1; i <= n; i++) f[i] = i;
    memset(fa, 0, sizeof(fa));
    memset(g, 0, sizeof(g));
}

int find(int x){
    if(f[x] == x)   return x;
    return f[x] = find(f[x]);
}

void kruskal(){
    init();
    for(int i = 1, from, to, ff, ft; i <= m; i++){
        from = a[i].u, to = a[i].v;
        ff = find(from), ft = find(to);
        if(ff != ft){
            f[ff] = ft;
            in(from, to, a[i].w);
            in(to, from, a[i].w);
        }
    }
}

void dfs(int rt){//建树
    vis[rt] = 1;
    for(int i = 1; i <= 16; i++){
        if(deep[rt] < (1<<i))   break;
        fa[rt][i] = fa[fa[rt][i-1]][i-1];
        g[rt][i] = min(g[rt][i-1], g[fa[rt][i-1]][i-1]);
    }
    for(int i = st[rt]; i; i = edge[i].next){
        int to = edge[i].v;
        if(vis[to]) continue;
        fa[to][0] = rt;
        g[to][0] = edge[i].w;
        deep[to] = deep[rt] + 1;
        dfs(to);
    }
}

int lca(int x, int y){//倍增往上跳,返回最近公共祖先 
    if(deep[x] < deep[y])   swap(x, y);
    int delta = deep[x] - deep[y];
    for(int i = 0; i <= 16; i++)//2^16正好过50000 
        if(delta & (1<<i))  x = fa[x][i];
    for(int i = 16; i >= 0; i--)
        if(fa[x][i] != fa[y][i]){
            x = fa[x][i];
            y = fa[y][i];
        }
    if(x == y)  return x;
    else return fa[x][0];
}

int ask(int x, int y){//询问路上满足条件的边 
    int mn = inf;
    int delta = deep[x] - deep[y];
    for(int i = 0; i <= 16; i++)
        if(delta & (1<<i)){
            mn = min(mn, g[x][i]);
            x = fa[x][i];
        }
    return mn;
}

int main(){
    cin >> n >> m;
    for(int i = 1, x, y, z; i <= m; i++)
        cin >> a[i].u >> a[i].v >> a[i].w;
    sort(a+1, a+m+1, cmp);
    kruskal();
    for(int i = 1; i <= n; i++)
        if(!vis[i]) dfs(i);
    cin >> q;
    while(q--){
        int op, ed;
        cin >> op >> ed;
        if(find(op) != find(ed)){
            cout << "-1" << endl;
            continue;
        }
        else{
            int baba = lca(op, ed);
            cout << min(ask(op, baba), ask(ed, baba)) << endl;
        }
    }
    return 0;
}

花匠

题目

花匠栋栋种了一排花,每株花都有自己的高度。花儿越长越大,也越来越挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致。
具体而言,栋栋的花的高度可以看成一列整数h1,h2..hn。设当一部分花被移走后,剩下的花的高度依次为g1,g2..gn,则栋栋希望下面两个条件中至少有一个满足:
条件 A:对于所有g(2i)>g(2i-1),g(2i)>g(2i+1)
条件 B:对于所有g(2i)<g(2i-1),g(2i)<g(2i+1)
注意上面两个条件在m = 1时同时满足,当m > 1时最多有一个能满足。
请问,栋栋最多能将多少株花留在原地。

输入输出格式

输入格式:
输入文件为 flower .in。
输入的第一行包含一个整数n,表示开始时花的株数。
第二行包含n个整数,依次为h1,h2..hn,表示每株花的高度。。
输出格式:
输出文件为 flower .out。
输出一行,包含一个整数m,表示最多能留在原地的花的株数。

输入输出样例

样例输入

5
5 3 2 1 2

样例输出

3

说明

样例解释
有多种方法可以正好保留 3 株花,例如,留下第 1、4、5 株,高度分别为 5、1、2,满足条件 B。
数据范围
对于 20%的数据,n ≤ 10;
对于 30%的数据,n ≤ 25;
对于 70%的数据,n ≤ 1000,0 ≤ ℎi≤ 1000;
对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤ hi≤ 1,000,000,所有的hi 随机生成,所有随机数服从某区间内的均匀分布。

思路

意思就是让你找转折点的数量即g(i-1)<gi>g(i+1)或g(i-1)>gi<g(i+1),gi即是转折点。首先可以肯定的是,当花的数目为1的时候,可以直接输出1。首尾两株花都肯定是可以留下的,因此,当花的数目大于等于2时,我们可以将答案初始化为2,然后加上转折点的数目即可

代码

#include<iostream>
#include<cstdio>
using namespace std;
int a[100005];
int w,n,ans;
int main(){
    scanf("%d",&n);
    a[0]=-147258963;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[++w]);
        if(a[w]==a[w-1]) --w;
    } 
    if(w==1) {
        printf("1");
        return 0; 
    }
    ans=2;
    for(int i=2;i<=w-1;i++){
        if (((a[i]>a[i-1])&&(a[i]>a[i+1]))||((a[i]<a[i-1])&&(a[i]<a[i+1]))){
            ans++;
        }
    }
    printf("%d",ans);
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容