NOIP模拟测试总结

排列树

1.1 description

小 C 最近喜欢上了树据结构。树据结构是由 n 个点组成的结构,每个点都有一个 1 ~ n 之中的标号,且保证 n 个点的标号形成一个 1 ~ n 的排列,其中有一个点为这个树据结构的根节点,其余 n-1 个点在树据结构中都有唯一的父亲。而且由于树据结构的特殊性质,这些父亲的关系并不会形成一个环。
小 C 发现了一种特别优美的树据结构,满足对于所有存在父亲的点,都满足父亲的标号小等于这个点的标号,小 C 把这种树据结构称作“排列树”。小 C 很快就发现了,n 个点的树据结构,排列树有 (n-1)! 个,不过现在小 C 想知道,在树据结构的形态一定时,排列树一共有多少个。
一句话题意:给你一棵以 1 号点为根的树,现在你要给每个点分配一个 1 ~ n 的标号,使得父节点的标号小于子节点的标号,问一共有多少种方案,你只需要输出在 mod 998244353 意义下的方案数即可。

1.2 input

第一行一个数 n。
接下来 n-1 行,每行两个数 ai,bi,表示树中存在 (ai,bi) 这条边。

1.3 output

输出一个数,表示合法的方案数。

1.4 sample input

4
1 3
3 2
1 4

1.5 sample output

3

1.6 data range

对于 20 % 的数据,n ≤ 10。
对于 50 % 的数据,n ≤ 200。
对于 70 % 的数据,n ≤ 3000。
5
对于 100% 的数据,n ≤ 10 。

题解

由题知“n 个点的树据结构,排列树有 (n-1)! 个"
记 szei 表示 i 号点的子树大小,gi 表示以 i 为根的子树分配 szei 个标号的方案数。gi 可以用所有儿子的方案数乘起来得到,这样儿子内部的标号是一定的。不过注意儿子之间的标号不一定,还要先给每个儿子分配标号,我们用组合数来分配一下即可。
时间复杂度 O(n)。
以下图为例

在这里插入图片描述
以3为根的子树,排列树有!1=1个;
以2为根的子树,排列树有!2=2个;
然后分配标号,以3为根的子树是从5个点中分配2个点,求得C(5,2)=10;
以2为根的子树是在以3为根的子树基础上分配,所以求得C(5,3+2)=1;
1的两个儿子合并得到整棵树的方案数为1 * 2 * 10 * 1=20。

复制的标程(应该是满分的)

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

const int RLEN=1<<18|1;
inline char nc() {
    static char ibuf[RLEN],*ib,*ob;
    (ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ib==ob) ? -1 : *ib++;
}
inline int rd() {//快读 
    char ch=nc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
    return i*f;
}

const int N=1e5+50, mod=998244353;
inline int mul(int x,int y) {return (long long)x*y%mod;}
inline int power(int a,int b,int rs=1) {for(;b;b>>=1,a=mul(a,a)) if(b&1) rs=mul(rs,a); return rs;}

struct binom {
    int fac[N], ifac[N];
    binom() {
        fac[0]=1;
        for(int i=1;i<N;i++) fac[i]=mul(fac[i-1],i);
        ifac[N-1]=power(fac[N-1],mod-2);
        for(int i=N-2;~i;i--) ifac[i]=mul(ifac[i+1],i+1);
    }
    inline int C(int a,int b) {return mul(fac[a],mul(ifac[b],ifac[a-b]));}
} C;
int n,sze[N],g[N];
vector <int> edge[N];

inline void dfs(int x,int f) {
    g[x]=1; 
    for(int e=0;e<edge[x].size();++e) {
        int v=edge[x][e]; if(v==f) continue;
        dfs(v,x); g[x]=mul(g[x],g[v]);
        g[x]=mul(g[x],C.C(sze[x]+sze[v],sze[x]));
        sze[x]+=sze[v];
    } ++sze[x];
}
int main() {
    //freopen("tree.in","r",stdin);
    //freopen("tree.out","w",stdout);
    scanf("%d",&n); 
    for(int i=1;i<n;i++) {
        int x, y;
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
        edge[y].push_back(x);
    } dfs(1,0);
    cout<<g[1]<<'\n';
}

自己打的

#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const ll q=1e6+10,qq=998244353;
struct node
{
	ll next,to;
}p[2*q];
ll h[2*q],cnt=1,n;
void add(int a,int b)
{
	++cnt;
	p[cnt].next=h[a];
	p[cnt].to=b;
	h[a]=cnt;
}
ll g[q],siz[q],jc[q];
void yv()
{
	jc[0]=1;
	for(int i=1;i<n;++i)
	jc[i]=jc[i-1]*i%qq;
}
ll quickpow(ll a,ll b,ll qq)
{
	//printf("quickpow %d^%d\n",a,b);
	ll r=1,base=a;
	while(b)
	{
		//printf("quickpow r=%d base=%d\n",r,base);
		if(b&1) r=r*base%qq;
		base=base*base%qq;
		b>>=1;
	}
	//printf("quickpow r=%d\n",r);
	return r%qq;
}
ll inv(ll x,ll qq)
{
	return quickpow(x,qq-2,qq);
}
ll C(ll a,ll b,ll qq)
{
	//printf("C a=%d b=%d\n",a,b);
	if(b>a) return 0;
	ll up=1,down=1;
	//for(int i=a-b+1;i<=n;++i) up=up*i%qq;
	//for(int i=1;i<=b;++i) down=down*i%qq;
	for(int i=a-b+1;i<=a;++i)
	{
		up*=i;up%=qq;
	}
	for(int i=1;i<=b;++i)
	{
		down*=i;down%=qq;
	}
	//printf("C up=%d down=%d\n",up,down);
	//printf("C up*inv(down,qq)%qq=%d\n",up*inv(down,qq)%qq);
	return up*inv(down,qq)%qq;
}
ll lucas(ll a,ll b,ll qq)
{
	//printf("lucas a=%d b=%d\n",a,b);
	if(b==0) return 1;
	return C(a%qq,b%qq,qq)*lucas(a/qq,b/qq,qq)%qq;
}
void dfs(ll x,ll fa)
{
	g[x]=1;
	for(int i=h[x];i;i=p[i].next)
	{
		int u=p[i].to;
		if(u==fa) continue;
		dfs(u,x);
		g[x]*=g[u];
		g[x]*=lucas(siz[x]+siz[u],siz[x],qq);
		//printf("sum=%d siz[x]=%d lucas=%d\n",siz[x]+siz[u],siz[x],lucas(siz[x]+siz[u],siz[x],qq));
		siz[x]+=siz[u];
	}
	++siz[x];
}
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<n;++i)
	{
		ll a,b;
		scanf("%lld%lld",&a,&b);
		add(a,b);
		add(b,a);
	}
	yv();
	//printf("%d\n",jc[n-1]);
	//printf("%lld\n",lucas(5,2,998244353));
	dfs(1,0);
	printf("%lld",g[1]);
}

字胡串

2.1 description
小 Z 最近喜欢上了字胡串。字胡串是一个由数字拼接而成的序列。具体的,用 S 来表示一个字胡串,Si 表示这个字胡串的第 i 位,Sl,r(1 ≤ l ≤ r ≤ n) 表示这个字胡串的一个子串,这个子串是由 Sl,Sl+1,…,Sr 依次拼接而成的一个字胡串。小 Z 自己定义了一个函数 f(S),其中:
f(S) = max(s1,s2,s3…sn)
他还定义了一个函数 g(S),其中:
g(S) = S1|S2|…|Sn
现在小 Z 想要问你,对于给定的字胡串 S,有多少个子串 T,满足 g(T) > f(T)。

2.2 input

第一行一个整数 n。
接下来一行 n 个整数,第 i 个数表示 Si。

2.3 output

输出一个数,表示满足条件的子串的个数。

2.4 sample input 1

5
3 2 1 6 5

2.5 sample output 1

8

2.6 sample input 2

4
3 3 3 3

2.7 sample output 2

0

2.8 data range

对于 20 % 的数据,n ≤ 300。
对于 50 % 的数据,n ≤ 3000。
5
对于 80 % 的数据,n ≤ 10 。
6 9
对于 100 % 的数据,n ≤ 10 ,ai ≤ 10 。

题解

考虑一个区间,只要这个区间的任意值非最大值有一位不与最大值相同,那么这个区间就是合法区间。
找出所有值左右第一个大于它的位置,那么以它为最大值的区间就固定在这一段中。只要再找出这个区间中左右第一个有一位不与最大值相同的值的位置,那么这个位置左边的所有位置都可以与最大值右边的位置构成一个合法区间。右边也同理。
可以用单调栈找出左右第一个大于它的位置,再用 O(n log ai) 的时间处理出左右第一个有有某一位为当前数超集的地方,然后就可以 O(n) 统计答案了,注意处理值相同的情况。
时间复杂度 O(n log ai)。
在这里插入图片描述
从如意区间开始,假设max1是该区间最大值,如果该区间有值在2进制上与max1的0位不同,则该区间为合法区间,以该区间向外扩展直到两边第一个大于max1的值,假设al,ar为第一个大于max1的值,则al,ar之内的区间都为合法区间,按相同的方法统计,直到找完。

标程

#include <bits/stdc++.h>
using namespace std;
inline int read(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
const int N=1e6+50;
int n,a[N];
int l[N],r[N];//左/右边第一个大于他前面的数的编号 
int st[N],pos[N],top;//求l[N]和r[N]的辅助数组 
int diff_l[N],diff_r[N];//左/右边第一个与i|大于i的数 
int mx[35];//求diff_l[N]和diff_r[N]的辅助数组 
//int mxpos[35];

int main(){
    //freopen("string.in","r",stdin);
    //freopen("string.out","w",stdout);
    n=read(); 
    for(int i=1;i<=n;i++)a[i]=read();
    //printf("%d\n",top);
    for(int i=1;i<=n;i++){
        while(top&&st[top]<=a[i])top--;
        l[i]=pos[top]+1;
        st[++top]=a[i];pos[top]=i;
        //printf("i=%d top=%d l[i]=%d\n",i,top,l[i]);
    }
    top=0; pos[top]=n+1;
    for(int i=n;i>=1;i--){
        while(top&&st[top]<a[i]) top--;
        r[i]=pos[top]-1;
        st[++top]=a[i];pos[top]=i;
        //printf("i=%d top=%d r[i]=%d\n",i,top,r[i]);
    }
    /*for(int i=1;i<=top;++i)
    printf("%d\n",st[i]);*/
    for(int i=1;i<=n;i++){
        int p=0;
        for(int j=0;(1ll<<j)<=a[i];j++){
            if((1ll<<j)&a[i])mx[j]=max(mx[j],i);
            else p=max(p,mx[j]);
        }
        //printf("l p=%d\n",p);
        diff_l[i]=p;
    }
    fill(mx,mx+32+1,n+1);
    for(int i=n;i>=1;i--) {
        int p=n+1;
        for(int j=0;(1ll<<j)<=a[i];j++) {
            if((1ll<<j)&a[i]) mx[j]=min(mx[j],i);
            else p=min(p,mx[j]);
        }
        diff_r[i]=p;
        //printf("r p=%d\n",p);
    }
    long long ans=0;
    for(int i=1;i<=n;i++) {
        if(diff_l[i]>=l[i])
            ans+=1ll*(diff_l[i]-l[i]+1)*(r[i]-i+1);
        if(diff_r[i]<=r[i])
            ans+=1ll*(r[i]-diff_r[i]+1)*(i-l[i]+1);
        if(diff_l[i]>=l[i]&&diff_r[i]<=r[i])
            ans-=1ll*(r[i]-diff_r[i]+1)*(diff_l[i]-l[i]+1);
    }
    cout<<ans<<endl;
}

有环无向图

3.1 description

小 L 最近喜欢上了图论,他最近在着重研究一类图,叫做有环无向图。有环无向图是由 n点,m 条边组成的无向联通图。且图中 可能存在环,但不可能存在重边或自环。
小 L 对这个图上的最短路问题产生了兴趣,于是他花了 3 分钟时间学习了 SFPA 算法。他得,这个最短路实在是太 Naiive 了!他决定对这个图改造一下。改造后,小 L 规定了对于一条径的权值为经过所有点的代价,其中起点的代价为起点的代价是离开起点的边的边权,终点的价是进入终点的边的边权,途中经过的点的代价是进入和离开这个点的两条边的边权的较大值小 L 赶紧用 SFPA 算法水过了这道题(当然,他是不会告诉你 SFPA 算法是怎么做的),后他找到了你,想要看看你有没有比他高明一些的方法能够求出从 1 号点到 n 号点的最短路

3.2 input

第一行两个数 n,m。
接下来 m 行,每行三个数 ai,bi,ci,表示 ai 和 bi 之间存在一条长度为 ci 的边。

3.3 output

输出一个数,表示 1 号点到 n 号点的最短路长度。

3.4 sample input

4 5
3 4 8
2 3 1
1 3 2
1 2 5
2 4 4

3.5 sample output

12

3.6 data range

对于 30 % 的数据,n ≤ m ≤ 3000。
5 9
对于 100 % 的数据,n ≤ m ≤ 2*10 ,ci ≤ 10 。

题解

考虑优化边数。对于一个点,先把出入边按权值排序,入边连向他的反向边,每个反向边往下连长度为 0 的边,向上连长度为两边差值的边,边数优化到了 O(m)。
时间复杂度 O((n+m)log n)。

以给的样例为例构图
在这里插入图片描述
自己结合图对这道题进行理解

标程

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
inline int read(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
const int Maxn=2e5+50,Maxm=4e5+50;
int n,m,nxt[Maxm],to[Maxm],val[Maxm],last[Maxn],ecnt=1,vis[Maxm],tot;
vector<pii>edge[Maxm];
struct E{
    int id,val;
    E(){}
    E(int x,int z):id(x),val(z){} 
    friend inline bool operator <(const E &a,const E &b){
        return a.val<b.val;
    }
}que[Maxm];
long long dis[Maxm];
inline void add(int x,int y,int w){
    nxt[++ecnt]=last[x];last[x]=ecnt;to[ecnt]=y;val[ecnt]=w;
}
priority_queue< pii,vector<pii>,greater<pii> >q;
inline void dij(){
    for(int i=2;i<=ecnt;i++) dis[i]=2e18;
    for(int e=last[1];e;e=nxt[e]){
        dis[e]=val[e];q.push(make_pair(dis[e],e));
    }
    while(!q.empty()){
        if(vis[q.top().second]){q.pop();continue;}
        int u=q.top().second;q.pop();vis[u]=1;
        for(int e=edge[u].size()-1;e>=0;e--){ 
            int v=edge[u][e].first,w=edge[u][e].second;
            if(vis[v])continue;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push(make_pair(dis[v],v));
            }
        }
    }
}
int main(){
   // freopen("graph.in","r",stdin);
  //  freopen("graph.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),w=read();
        add(x,y,w);add(y,x,w);
    }
    for(int i=2,tp=0;i<n;tp=0,i++){
        for(int e=last[i];e;e=nxt[e])
            que[++tp]=E(e,val[e]);
        sort(que+1,que+tp+1);
        for(int j=1;j<=tp;j++){
            if(j!=1)edge[que[j].id].push_back(make_pair(que[j-1].id,0));
            if(j!=tp)edge[que[j].id].push_back(make_pair(que[j+1].id,que[j+1].val-que[j].val));
            edge[que[j].id^1].push_back(make_pair(que[j].id,que[j].val));
        }
    }
    dij();
    long long ans=2e18;
    for(int e=last[n];e;e=nxt[e])
        ans=min(ans,dis[e^1]+val[e]);
    cout<<ans<<endl;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值