NOIP模拟题 [LIS][建图][递推][容斥]

5 篇文章 0 订阅
4 篇文章 0 订阅

正确评估题目难度。

T1:
题意:
给定一棵树,求使得每一个节点的左子树任意值都比当前节点值小,右子树任意值都比当前节点大所需要的最小改动点数。
分析:
首先对于这种神奇的递归定义,我们通过传递大小关系可以发现,通过上推我们可以确定任意两点之间的大小关系,然后很容易想到把树上的点排序。
所以如果先忽略掉“严格递增”和“可能中间不能加数”,这道题显然可以直接贪心减去LIS。
那么这种算法会遇到什么问题呢,显然就是可能有的序列中间是不能加数的!
那么怎么解决呢,很容易可以发现两个之间可以有的数是两数差
于是,当当当,算法出来了,就减一下就可以啦(其实也可以不减,每次比较,但是非常麻烦!预处理大法好!)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<ctime>
#define ll long long 
#define inf 2e18
#define clr(x) memset(x,0,sizeof(x))
#define maxen(x) memset(x,127,sizeof(x))
#define maxer(x) memset(x,31,sizeof(x))
#define minus(x) memset(x,-1,sizeof(x))
#define each(i,n,m) for(int i=n;i<m;i++)
#define eachrev(i,n,m) for(int i=n;i>m;i--)
#define minn(a,b,c) min(a,min(b,c))
#define maxx(a,b,c) max(a,max(b,c))
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define PROC "tree"
//for(int i=1;i<=n;i++)
//(double) (ll) LL (int)
//(double)clock()/CLOCKS_PER_SEC
using namespace std;
const int Maxn=1e5+5;
const int modd=1e9+7;
int n,tmp1,tmp2,idx;
int a[Maxn],rs[Maxn],ls[Maxn],rk[Maxn];
ll f[Maxn];
int cnt;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void init()
{
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
        for(int i=2;i<=n;i++){
            tmp1=read();tmp2=read();
            if(tmp2)rs[tmp1]=i;
            else ls[tmp1]=i;
    }
}
void dfs(int u)
{
    if(ls[u])dfs(ls[u]);
    rk[++idx]=u;
    if(rs[u])dfs(rs[u]);
}
void work()
{
    dfs(1);f[cnt+1]=inf;
    for(int i=1;i<=n;i++){
        a[rk[i]]-=i;
        int pos=upper_bound(f+1,f+cnt+2,a[rk[i]])-f;
        f[pos]=a[rk[i]];
        if(pos>cnt){
            cnt++;f[cnt+1]=inf;
        }
    }
    printf("%d",n-cnt);
}
void debug()
{
    //
}
int main()
{
    freopen(PROC".in","r",stdin);
    freopen(PROC".out","w",stdout);
    init();
    work();
    //debug();
    return 0;
}

T2:
题意:
给定一些数,求最后一个不能被组合出来的数。
分析:
久见此题,第一次真的写。
首先我们可以知道一个简单的判定,即如果一个大于等于最小单个数的区间都可以被组成的话,显然后面就都可以被组成了。
然后我们考虑“缩点”,因为mod最小数的值一样的点之间可以通过加上最小数到达,所以可以放在一个集合,只需要求出每个集合能被组成的最小数,则这个集合剩下的数都可以被组成。
即:根据相同性质减少工作量;
然后算出到每个集合的最短路(即最小能被组成的数)即可。
SPFA大法好,再写错我就去撞墙。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<ctime>
#define ll long long 
#define inf 2e8
#define clr(x) memset(x,0,sizeof(x))
#define maxen(x) memset(x,127,sizeof(x))
#define maxer(x) memset(x,31,sizeof(x))
#define minus(x) memset(x,-1,sizeof(x))
#define each(i,n,m) for(int i=n;i<m;i++)
#define eachrev(i,n,m) for(int i=n;i>m;i--)
#define minn(a,b,c) min(a,min(b,c))
#define maxx(a,b,c) max(a,max(b,c))
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define PROC "bullpen"
//for(int i=1;i<=n;i++)
//(double) (ll) LL (int)
//(double)clock()/CLOCKS_PER_SEC
using namespace std;
const int Maxn=3e3+5;
const int modd=1e9+7;
int h[Maxn],usef[Maxn],f[Maxn],mapy[Maxn][Maxn];
int n,m,tmp,idx,ans,s,p=2e9;
queue<int>que;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
void init()
{
    n=read();
    m=read();
    maxen(usef);
    maxer(f);
    maxer(mapy);
    for(int i=1;i<=n;i++){
        tmp=read();
        for(int j=0;j<=m;j++)
        if(j<tmp){h[tmp-j]=1;p=min(p,tmp-j);}
        else break;
    }
    s=p;
    for(int i=1;i<=3000;i++)
    if(h[i]){
        s=gcd(i,s);
        usef[i%p]=min(usef[i%p],i);
    }
}
void work()
{
    if(s!=1||p==1){
        printf("-1");
        return;
    }
    for(int i=0;i<p;i++)
        for(int j=1;j<p;j++)
            if(i==j)mapy[i][j]=1;
            else if(usef[(j-i+p)%p])
            mapy[i][j]=usef[(j-i+p)%p];
    que.push(0);f[0]=0;
    while(!que.empty()){
        int cur=que.front();que.pop();
        for(int j=1;j<p;j++)
            if(f[j]>f[cur]+mapy[cur][j]){
                f[j]=f[cur]+mapy[cur][j];
                que.push(j);
            }
    }
    for(int i=1;i<p;i++)
        if(f[i]<=9e6)
        ans=max(ans,f[i]-p);
    printf("%d",ans);
}
void debug()
{
    //
}
int main()
{
    freopen(PROC".in","r",stdin);
    freopen(PROC".out","w",stdout);
    init();
    work();
    //debug();
    return 0;
}

T3:
题意:
求k种元素,每种N个,组成长【k,n】的,每种至少一个的序列的方案数。
分析:
考虑到每种都只要有一种,则可以很容易得到递推式。
然后考虑容斥原理,因为如果我们直接算,则可能会有一些相同的情况被重复计算,然后就减去了不成立的情况的情况(但是不会重复算,即不会对相同序列算两次!这是排列的性质,要想清楚啊!)
然后用容斥减去不成立的情况即可。
做递推不要玄学啊。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<ctime>
#define ll long long 
#define inf 2e8
#define clr(x) memset(x,0,sizeof(x))
#define maxen(x) memset(x,127,sizeof(x))
#define maxer(x) memset(x,31,sizeof(x))
#define minus(x) memset(x,-1,sizeof(x))
#define each(i,n,m) for(ll i=n;i<m;i++)
#define eachrev(i,n,m) for(ll i=n;i>m;i--)
#define minn(a,b,c) min(a,min(b,c))
#define maxx(a,b,c) max(a,max(b,c))
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define PROC "pearl"
//for(ll i=1;i<=n;i++)
//(double) (ll) LL (ll)
//(double)clock()/CLOCKS_PER_SEC
using namespace std;
const ll Maxn=35;
const ll modd=1234567891;
ll t,n,k;
ll ans;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void init()
{
    t=read();
}
ll qm(ll a,ll b)
{
    ll ret=1;
    while(b){
        if(b&1)(ret*=a)%=modd;
        (a*=a)%=modd;
        b>>=1;
    }
    return ret;
}
ll jc(ll n)
{
    ll ret=1;
    for(ll i=1;i<=(ll)n;i++)
        (ret*=i)%=modd;
    return ret;
}
void work()
{
    for(ll i=1;i<=t;i++){
        n=read();
        k=read();
        ans=0;
        ll  flg=1;
        for(ll j=0;j<k-1;j++){
            ll c=jc(k)*qm(jc(j),modd-2)%modd*qm(jc(k-j),modd-2)%modd;
            ll lc=qm(k-j,k)*(qm(k-j,n+1-k)%modd+modd-1LL)%modd*qm(k-j-1,modd-2)%modd;
            (ans+=(c*lc%modd)%modd*flg+modd)%=modd;
            flg=0-flg;
        }
            (ans+=(ll)k*(ll)(n-k+1)%modd*flg+modd)%=modd;
            printf(lld"\n",ans);
    }
}
void debug()
{
    //
}
int main()
{
    freopen(PROC".in","r",stdin);
    freopen(PROC".out","w",stdout);
    init();
    work();
    //debug();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值