noip模拟赛总结

noip 2014 day1

总:忘了快速幂,离散化不熟,读不懂题(其实还是知识点不熟,基础不牢吧)


1.转圈游戏

读题可以发现

0->m           m   = ( 0+m)%n

1->m+1         m+1 = (m+1)%n

...                ...

n-m->0         0   = (n-m+m)%n

n-m+1->1       1   = (n-m+1+m)%n

哇塞好像发现了什么不得了的事情

某人转一次之后的位置编号就是现在的位置编号加上m再对n取模

好啦,题目给了总人数n,给了m,给了转的总轮数10k,还给了某人当前位置编号x

emmm....好像不太对,k<109,所以......轮数是<10k^9 的.....这数有点大啊....

emm....嗷我突然想起来一个小剪枝:

他们转转转总会有转到原位置的一次吧,嗯对,那就先找到他转到原位置需要几轮,再用总轮数对它取模,

得到的就是实际需要看的轮数了,因为那些转转转都回到了原位置等于白转

所以,我就打了这样的代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int n,m,k,x,now/*现在在几号位置*/,per/*转回原位置需要的轮数*/;
ll tot=1;//去掉转回原位置的所有轮数,剩余实际需要看的 

int main(){
    scanf("%d%d%d%d",&n,&m,&k,&x);
    for(int i=0;i<n;i++){
        now=(now+m)%n;
        per++;
        if(now==0) break;
    }
    for(int i=1;i<=k;i++)
        tot=tot*10%per;
    while(tot--)
        x=(x+m)%n;
    printf("%d",x);
    return 0;
}

然后,成功T了一个点。。。

然鹅,,,快速幂啊!!!也不用取余了,直接快速幂求10k,再套用公式,只要随时取模,就不会爆。。米有想到快速幂

某人转一次之后的位置编号就是现在的位置编号加上m再对n取模,转10就是现在的位置编号加上m的10次方就好了

这样就A了嘛。。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 

int n,m,k,x;
ll ans;

int qpow(int a, int b, int q) {
    ll s = 1;
    while (b) {
        if (b & 1)
            s = s * a % q;
        a = (ll)a * a % q;
        b >>= 1;
    }
    return s;
}

int main(){
    scanf("%d%d%d%d",&n,&m,&k,&x);
    ans=( x%n + m%n * qpow(10,k,n)%n ) %n;
    printf("%lld",ans);
    return 0;
}

2.火柴排队

就是让a,b数组中第i大的两个对应,答案就是最小的了

关键关键!u[b[i].xu]=a[i].xu

这利用了下标的单调升序。

可以发现u数组下标都是1~n,b数组中每个数的顺序也都是1~n的正整数,a数组同样如此。

那么对u数组进行归并排序之后,可以发现u数组中的值就是:u[1]=1,u[2]=2......u[n]=n对吧?

那么也就是说,对u数组进行归并排序就是为了让u[i]=i,也就是说,让b数组中第i小的数和a数组中第i小的数在同一个位置。

再加上归并排序模板就好了哈哈

#include<bits/stdc++.h>
using namespace std;

const int mod=99999997;
const int N=1000005;
int n,u[N],v[N];
long long cnt;

struct node{
    int h,xu;
}a[N],b[N];

bool cmp(node x,node y) { return x.h < y.h;}

void merge_sort(int l, int r) { //归并排序模板呐~~哈哈 
    if(l<r) {
        int mid=(l+r)/2;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        int k=l;
        int p=l,q=mid+1;
        while(p<=mid||q<=r){
            if(q>r||(p<=mid&&u[p]<=u[q]))
                v[k++]=u[p++];
            else {
                v[k++]=u[q++];
                cnt += (mid-p+1);  //将逆序对的个数累加起来
                cnt %= mod;
            }
        }
        for (int i=l;i<=r;i++) 
            u[i]=v[i];
    }
}

int main(){
    freopen("match.in","r",stdin);
    freopen("match.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i].h);
        a[i].xu=i;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){
        scanf("%d",&b[i].h);
        b[i].xu=i;
    }
    sort(b+1,b+n+1,cmp);
    for(int i=1;i<=n;i++)
        u[b[i].xu]=a[i].xu; 
    merge_sort(1,n);
    printf("%lld",cnt);
    return 0;
}

3.货车运输

题目描述

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

输入格式

第一行有两个用一个空格隔开的整数n,m,表示 A 国有n 座城市和 m 条道路。

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

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

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

输出格式

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

这个。。emmm...不会。。实际上题目也没太读懂。。

求助了yxt dalao,终于理解了题意,但做法大体会了一点,还是好多点不懂


noip 2015

day1总:

一开始,,文件夹交多了。。。

重交了一遍。。

发现,freopen写错了。。

 

并查集没学好,不会并查集判环

模拟。。。好复杂

 


 

1.神奇的幻方

就是裸裸的的模拟吧(我是这样做的)

map1存的幻方上的数,used看有没有填过

嗯,就这样,再看题目怎么说的,怎么打就好了

 

2.信息传递

。。没有做出来,原本想用vector把每一轮每个人收到的生日信息,都记下来,遇到自己的生日就结束了

但不会做。。。

看了下题解,还没看懂,题解好多用了并查集判环搞了半天才弄懂

/*如果有两个点祖先节点相同,那么就可以
构成一个环,长度为两个点到祖先节点长度之和+1*/
#include<bits/stdc++.h>
using namespace std;

const int N = 200003;
int n, t, fa[N]/*祖先节点*/, d[N]/*到祖先的路径长*/, minn; 

int find(int x) { 
    if (fa[x] != x) {
        int last = fa[x]; //不能先改d 
        fa[x] = find(fa[x]); //更新祖先节点
        d[x] += d[last]; //更新路径长
    }
    return fa[x];
}

void check(int a,int b) { //判环 
    int x = find(a), y = find(b);
    if(x != y) { //不是环,则连接两点,更新父节点和路径长
        fa[x] = y;
        d[a] = d[b] + 1;
    } else {  //x=y时,形成环,更新最小环长度 
        minn = min(minn, d[b] + 1); //更新最小环长度
    }
    return;
}

int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    minn = 0x7777777;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &t);
        check(i, t);
    }
    printf("%d", minn);
    return 0;
}

3.斗地主

额,,这个毒瘤模拟。。。这个情况分的很蒙

前两个做了好长时间(虽然第二题还没做出来),然后,第三题就放弃了

 

到现在还没做出来。。


day2总:

emmm...二分不太会,,,导致第一题做了好久

看了下第二题,得出结论:不会。。直接部分分吧,就打了个k=1的,但是我发现,有时候对,有时候不对,最终还是得了10分

第三题,我就直接看字任务吧,发现有m=1的,这样直接找两个点之间的最短路,然后减去路径上的最大边权,就好了吧

然后我开始打。。。打着打着,发现。。。最短路不会打。。。

 


 

1.跳石头

 二分

[  使用二分的情况:

满足两个条件。一个是有界,一个是单调。

如果题目规定了有“最大值最小”或者“最小值最大”的东西,那么这个东西应该就满足二分答案的有界性(显然)和单调性(能看出来)。 ]

 

二分跳跃距离,然后把这个跳跃距离“认为”是最短的跳跃距离,然后去以这个距离为标准移石头。使用一个check判断这个解是不是可行解。如果这个解是可行解,那么有可能会有比这更优的解,那么我们就去它的右边二分。为什么去右边?答案是,这个区间是递增的 ,而我们求的是最短跳跃距离的最大值,显然再右边的值肯定比左边大,那么我们就有可能找到比这更优的解,直到找不到,那么最后找到的解就有理由认为是区间内最优解。反过来,如果二分到的这个解是一个非法解,我们就不可能再去右边找了。因为性质,右边的解一定全都是非法解。那么我们就应该去左边找解。整个过程看起来很像递归,这个过程可以递归写, 也可以写成非递归形式.

check函数:想办法检测这个解是不是合法。判断如果以这个距离为最短跳跃距离需要移走多少块石头,先不必考虑限制移走多少块,等全部拿完再把拿走的数量和限制进行比对,如果超出限制,那么这就是一个非法解,反之就是一个合法解.

可以去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,那这就是一个不合法的石头,应该移走。为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到i = n+1,为啥是n+1?河中间有n块石头,显然终点在n+1处。

模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。如果超了就返回false,不超就返回true。

 

2.子串

 DP

动态转移方程

  • f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1],翻译是 如果第i位不使用,那么方案数就是 第i-1位未使用时,前i-1位匹配前j位使用k个子串 + 第i-1位使用时,前i-1位匹配前j位使用k个子串。

  • **(a[i]==b[j])f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1] 翻译是 如果第i位使用,意味着a[i]==b[j],那么方案数就是 第i-1位未使用而第i位作为一个新子串 + 第i位和前面连在一起,不单独使用 + 第i-1位使用了,但第i位仍作为一个新子串。

这样转移方程就出来了。

我们再来思考边界条件。边界条件两种情况。

  • 对于A子串前i位,匹配B串第1个字符,那么只可能使用1个字符串,这种情况如果第i位不进行匹配,那么方案数就是之前所有能与第1位匹配的字符,所以f[i][1][1][0]=sum(a[1~i-1]==b[1])。

  • 同上,但如果B串第一位和A串第一位匹配了话,那么f[i][1][1][1]=1是显然的。

#include<bits/stdc++.h>

using namespace std;

const int mod = 1e9 + 7;
int n, m, k;
char a[1005], b[205];
int f[2][201][201][2], s, now, pre=1;//s是用来记录之前有多少个A串字符能和B串第一个字符匹配

int main(){
    scanf("%d %d %d", &n, &m, &k);
    scanf("%s %s", a + 1, b + 1);
    
    for (int i = 1; i <= n; i++){
        swap(now, pre); //交换,只要i改变
        f[now][1][1][0] = s;//边界1
        
        if (a[i] == b[1]) {
            f[now][1][1][1] = 1;
            s++;//边界2,解决了s统计的问题
        }
            
        for (int j = 2; j <= m; j++)
            for (int t = 1; t <= k; t++){
                if (a[i] == b[j]) //第i位用 
                    f[now][j][t][1]=((f[pre][j-1][t-1][1]+f[pre][j-1][t][1])%mod+f[pre][j-1][t-1][0])%mod;
                //此时有i-1用或不用,i与前面相连或不相连,但i-1不用时,i不可能与前面相连了,所以共三种情况
                f[now][j][t][0] = (f[pre][j][t][0] + f[pre][j][t][1]) % mod;
                //第i位不用时,包括i-1用和i-1不用 
            }
        
        for(int j = 1; j <= m; j++)
              for(int t = 1; t <= k; t++)
                f[pre][j][t][1] = f[pre][j][t][0] = 0;//清空。。
                //滚动数组,用前一个状态来代替后一个状态,后一个状态需要现在的状态,但前一个状态就不需要了。。
    }
    
    printf("%d", (f[now][m][k][1] + f[now][m][k][0]) % mod);

    return 0; 
}

 

3.运输计划

这个,,据说是LCA(求链长) + 二分

但我还不会。。

 

 

原文地址:https://www.cnblogs.com/aprincess/p/11836787.html