noip错题集

 

noip再刷题

 

 

 

解方程 用了秦九昭算法

 

f(x)=0,f(x%p)=0,缩小数据范围的思想,就像以前一到考对数的题,p最好取可靠的质数。

 

 

 

天天爱跑步 树上差分,一次性统计

 

 

 

运输计划 树上差分

 

 

 

飞扬的小鸟  第一次做是完全背包和01背包顺序弄反,这一次做的时候更新有问题,只枚举了下界到上界的更新,在完全背包的时候必须从一开始,而不能从下界开始(思考为什么)

 

 

 

蚯蚓 数学证明,两个优先队列,对于在变化的增量, 我们可以在入队时提前减去贡献,就不用考虑取出时的变化,最后的三个队列不能sort,复杂度会爆

 

 

 

斗地主 只是一张或两张牌的情况不应该搜

 

 

 

逛公园 最短路,计划化搜索,对于从终点还可以走回去的情况,新建一个最终原点

 

 

 

宝藏 一道很好的状压

 

 

 

花匠 我用的线段树dp,实际上可以贪心

 

 

 

华容道 其实分的最短路还是不错了

 

 

 

 

攻击装置

部落战争 

https://www.cnblogs.com/EdSheeran/p/8718577.html

forgetten one:以上两道是二分图匹配,有一个经典模型最小路径覆盖;

二分图最大匹配=最小点覆盖=总点数-最小点独立集;

大致证明:

最大匹配=最小点覆盖:如果还能匹配,那就证明还有边没被覆盖。

最小点覆盖=n-最大点独立集:如果独立集可以增大k,那就证明有k个点间两两没边,那最小点覆盖就可以删去这k个点。N个点,去掉最小点覆盖的,剩下的两两没边,就是最大点独立集。

最小边覆盖=n-最大匹配:本来是n条边(每个点要被一条边覆盖),每匹配一对,就相当于减少了一条边。

 

最小路径覆盖:最坏情况是一个点一个覆盖,每覆盖一次路径数就减少1,尽可能匹配多的边,每匹配一次相当于融合一次点;

相互攻击:找最小点覆盖,我们希望删去一些点,这些点攻击的人很多,又要使这些点尽量少,所以就是最小点覆盖;

 

count 数字计数

拆开考虑的数位DP,存了一个sum,表示s在当前数中出现的次数,到底就统计sum的贡献,加上这一维是因为他<=数位

 

骑士

画图得到经典性质:树+最多一个环,如果没有环就是裸上司的舞会;

有环拆成树,强制不走换上的边,从两边各做一次强制祖先不选的舞会,此题还要开longlong,又忘了;

 

安慰奶牛cheer 我可能是个傻逼,一道黄题想不出来;

最小生成树:关键是把点权划归在边权上,边走两次,点会对每条边进行贡献;

 

The Captain 寒假最后一测原题, dijistra的板子; 题意求:边权min(|xi-xj|, |y1-yj|),求起点到终点的最短路;

https://www.cnblogs.com/EdSheeran/p/8496929.html

 

Eat the Trees 轮廓线DP裸题;(不是思维,难在代码)

每次轮廓线的代码是最难的,轮廓线设计的方式很重要,决定了代码的难度,这题的巧妙之处在于直接把轮廓线对应的位置进行翻转:

#include<bits/stdc++.h>
using namespace std;
#define ex(i, u) for(int i = h[u]; i; i = G[i].nxt)
#define ll long long
const int M = (1<<12) + 5;
ll dp[13][13][M];
bool mp[13][13];
int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
    return x*=f;
}


int main(){
    //freopen("cc.out","w",stdout);
    int T, idc = 0;
    scanf("%d", &T);
    while(T--){
        int n, m;
        scanf("%d%d", &n, &m);
        memset(dp, 0, sizeof(dp));
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++) scanf("%d", &mp[i][j]);
        dp[0][m][0] = 1;
        for(int i = 1; i <= n; i++){
            
            /*for(int s = 0; s < 1<<(1+m); s++)
                dp[i][0][s>>1] = dp[i-1][m][s]; */
                for(int s = 0; s < (1<<m); s++)
                dp[i][0][s<<1] = dp[i-1][m][s];

            
            for(int j = 1; j <= m; j++) 
                for(int s = 0; s < 1<<(1+m); s++){
                    int p1 = 1<<(j-1), p2 = 1<<j;
                    if(mp[i][j]){
                        if((p1&s) && (p2&s)) dp[i][j][s] = dp[i][j-1][s^p1^p2];
                        else if(!(p1&s) && !(p2&s)) dp[i][j][s] = dp[i][j-1][s^p1^p2];
                        else dp[i][j][s] = dp[i][j-1][s] + dp[i][j-1][s^p1^p2];
                        
                    }
                    else{
                        if(!(p1&s) && !(p2&s)) dp[i][j][s] = dp[i][j-1][s];
                    }
                    //if(dp[i][j][s])printf("%d %d %d %lld\n",i, j, s, dp[i][j][s]);
                }
        }
        printf("Case %d: There are %I64d ways to eat the trees.\n", ++idc, dp[n][m][0]);
    }
}

 

两双手 思维DP;

排序+容斥

为什么排序后不考虑从后面回来呢? 由于坐标化简后相当于只向右上走,所以按坐标排序,只有排在它之前的点可能到达它

 

 

假面舞会: 一道探究性质的图论好题

https://blog.csdn.net/sunshinezff/article/details/48628401

两个难点:对环和链性质的分类讨论,建立-1的边保证找到环的起点和终点;

 

绝世好题

https://vjudge.net/contest/263626#problem/A

给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len)。

n<=100000,ai<=2*10^9

题解:很明显是一个拆位建边的题,但这样肯定TT;

可以直接DP,我上次没有深刻理解,这次理解了,但自己实现的时候还是思考不够深刻;

单独一位直接往下扫就好了,但很多时候边都是交错的,我自以为解决了这个问题,其实不然;

很简单的例子:2 3 1;

把WA和AC的对比一下;

WA

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5 + 5;
struct node{int w, t;}q[M];
int a[M], f[M];
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)scanf("%d", &a[i]), f[i] = 1;
    int ans =  0;
    for(int i = 0; i <= 31; i++){
        int lst = 0;
        for(int j = 1; j <= n; j++){
            if(a[j] & (1LL<<i)){
                f[j] = max(f[j], f[lst]+1);
                if(f[j] > f[lst]) lst = j;    
            } 
        }
    }
    for(int i = 1; i <= n; i++) ans = max(ans, f[i]);
    printf("%d\n", ans);
}

AC

#include<bits/stdc++.h>
using namespace std;
int f[33];
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        int mx = 0, x;
        scanf("%d", &x);
        
        for(int i = 0; i <= 31; i++)
            if(x & (1<<i)) mx = max(mx, f[i]);
        for(int i = 0; i <= 31; i++)
            if(x & (1<<i))f[i] = max(f[i], mx + 1);
    }
    
    int ans = 0;
    for(int i = 0; i <= 31; i++) ans = max(ans, f[i]);
    printf("%d\n", ans);
}

 

D - BLO

这题是原题,是对tarjan点双联通的一个运行过程的理解,一道好题;

可以参看以前写的:https://www.cnblogs.com/EdSheeran/p/9029346.html

 

 

Check the difficulty of problems

http://poj.org/problem?id=2151

一道概率DP,以前写过,但现在一点思路都没有,概率期望问题很大,而且因为玄学问题WA了一上午; 

 

题意:
ACM比赛中,共M道题,T个队,pij表示第i队解出第j题的概率
问 每队至少解出一题且冠军队至少解出N道题的概率。

解析:DP
设dp[i][j][k]表示第i个队在前j道题中解出k道的概率
则:
dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]);
先初始化算出dp[i][0][0]和dp[i][j][0];
设s[i][k]表示第i队做出的题小于等于k的概率
则s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k];

则每个队至少做出一道题概率为P1=(1-s[1][0])*(1-s[2][0])*```(1-s[T][0]);
每个队做出的题数都在1~N-1的概率为P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]);

最后的答案就是P1-P2
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
using namespace std;

double dp[1010][50][50];
double s[1010][50];
double p[1010][50];
int main()
{
    int M,N,T;
    while(scanf("%d%d%d",&M,&T,&N)!=EOF)
    {
        if(M==0&&T==0&&N==0)break;
        for(int i=1;i<=T;i++)
          for(int j=1;j<=M;j++)
           scanf("%lf",&p[i][j]);
        for(int i=1;i<=T;i++)
        {
            dp[i][0][0]=1;
            for(int j=1;j<=M;j++)dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]);

            for(int j=1;j<=M;j++)
              for(int k=1;k<=j;k++)
                dp[i][j][k]=dp[i][j-1][k-1]*p[i][j]+dp[i][j-1][k]*(1-p[i][j]);

            s[i][0]=dp[i][M][0];
            for(int k=1;k<=M;k++)s[i][k]=s[i][k-1]+dp[i][M][k];
        }
        double P1=1;
        double P2=1;
        for(int i=1;i<=T;i++)
        {
            P1*=(1-s[i][0]);
            P2*=(s[i][N-1]-s[i][0]);
        }
        printf("%.3lf\n",P1-P2);
    }
    return 0;
}
View Code

 

 

 

posted @ 2018-10-16 17:53  Ed_Sheeran  阅读(81)  评论(0编辑  收藏  举报