培训期间(1)

模拟考试

排列数

1.1 desctiption

小 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。
对于 100% 的数据,n ≤ 1e5。

题解

20分思路
搜索剪枝或者状压 DP。
100分的话
记 szei 表示 i 号点的子树大小,gi 表示以 i 为根的子树分配 szei 个标号的方案数。
gi 可以用所有儿子的方案数乘起来得到,这样儿子内部的标号是一定的。不过注意儿子之间
的标号不一定,还要先给每个儿子分配标号,我们用组合数来分配一下即可。
时间复杂度 O(n)。
以下见代码

#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);
    n=rd();
    for(int i=1;i<n;i++) {
        int x=rd(), y=rd();
        edge[x].push_back(y);
        edge[y].push_back(x);
    } dfs(1,0);
    cout<<g[1]<<'\n';
}

字胡串

2.1 description

小 Z 最近喜欢上了字胡串。字胡串是一个由数字拼接而成的序列。具体的,用 S 来表示一个
字胡串,Si 表示这个字胡串的第 i 位,Sl,r(1 ≤ l ≤ r ≤ n) 表示这个字胡串的一个子串,这个子
串是由 Sl,Sl+1,…,Sr 依次拼接而成的一个字胡串。
小 Z 自己定义了一个函数 f(S),其中:
n
f(S) = max S
i=1 i
他还定义了一个函数 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 。

题解

50分
直接 O(n2) 枚举区间,O(1) 判断是否合法即可。
100分
考虑一个区间,只要这个区间的任意值非最大值有一位不与最大值相同,那么这个区间就是
合法区间。
找出所有值左右第一个大于它的位置,那么以它为最大值的区间就固定在这一段中。只要再
找出这个区间中左右第一个有一位不与最大值相同的值的位置,那么这个位置左边的所有位置都
可以与最大值右边的位置构成一个合法区间。右边也同理。
可以用单调栈找出左右第一个大于它的位置,再用 O(n log ai) 的时间处理出左右第一个有有
某一位为当前数超集的地方,然后就可以 O(n) 统计答案了,注意处理值相同的情况。
时间复杂度 O(n log ai)。
以下见代码

#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],l[N],r[N],st[N],pos[N],top,diff_l[N],diff_r[N],mx[35];
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();
    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;
    }
    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;
    }
    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]);
        }
        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;
    }
    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。
对于 100 % 的数据,n ≤ m ≤ 2*1e5 ,ci ≤ 1e9

题解

30分
对于每个点可以 O(deg2i) 建图,然后跑最短路,时间复杂度 O(m2log m)。
100分
考虑优化边数。对于一个点,先把出入边按权值排序,入边连向他的反向边,每个反向边往下
连长度为 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;
}

感想

整个机房最高分50,这几道题考试的时候真的是文思如泉涌,看着不怎么难然而码力不够,有想法却不会构建代码,没看到我多少分,但大概也不咋样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值