T1 !! 排列树
1.1 description
小 C 最近喜欢上了树据结构。树据结构是由 n 个点组成的
中的标号,且保证 n 个点的标号形成一个 1 ~ n 的排列,其中有
其余 n-1 个点在树据结构中都有唯一的父亲。而且由于树据结
并不会形成一个环。
小 C 发现了一种特别优美的树据结构,满足对于所有存在父
于这个点的标号,小 C 把这种树据结构称作“排列树”。小 C 很
排列树有 (n-1)! 个,不过现在小 C 想知道,在树据结构的形态
一句话题意:给你一棵以 1 号点为根的树,现在你要给每个
父节点的标号小于子节点的标号,问一共有多少种方案,你只需要
的方案数即可。
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^5。
啊啊啊简单分析之后,发现这是一个以树的结构为对象的计数问题。
计数问题毫无疑问,通常要有子结构问题性质。
子问题就是单点ans,再到子树ans,最后到树的ans
也就是递归得到
一点点把树合并
代码~
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=1e5+50;
int n;
int read()
{
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();
}while(c>='0' && c<='9'){x=10*x+c-'0';c=getchar();
}return x*f;
}
int mul(int x,int y) {return (long long)x*y%mod;}
int quickpow(int a,int b)
{
int r=1;
while(b)
{
if(b&1) r=mul(r,a);
a=mul(a,a);
b>>=1;
}
return r;
}
int g[maxn],size[maxn];
struct node
{
int jc[maxn],inv[maxn];
node()
{
jc[0]=1;
for(int i=1;i<maxn;++i)
{
jc[i]=mul(jc[i-1],i);
}
inv[maxn-1]=quickpow(jc[maxn-1],mod-2);
for(int i=maxn-2;i>=0;--i)
{
inv[i]=mul(inv[i+1],i+1);
}
}
int C(int a,int b){return mul(jc[a],mul(inv[b],inv[a-b]));
}
}C;
vector<int> mp[maxn];
void dfs(int x,int fa)
{
g[x]=1;
for(int i=0;i<mp[x].size();++i)
{
int y=mp[x][i];if(y==fa ) continue;
dfs(y,x);g[x]=mul(g[x],g[y]);
// cout<<"size:::::"<<size[x]<<endl;
g[x]=mul(g[x],C.C(size[x]+size[y],size[x]));
size[x]+=size[y];
}++size[x];
}
main()
{
n=read();
for(int i=1;i<n;++i)
{
int x=read(),y=read();
mp[x].push_back(y);
mp[y].push_back(x);
}dfs(1,0);
for(int i=1;i<=10;++i)
{
for(int j=1;j<=i;++j)
{
//cout<<"i=="<<i<<" j=="<<j<<" C="<<C.C(i,j)<<endl;
}
}
//for(int i=1;i<=4;++i)
//{
// cout<<"g::"<<g[i]<<endl;
//}
// cout<<quickpow(2,5)<<endl<<endl;
cout<<g[1];
}
在计算ans时,用ans[i]和ans【j】合成ans【k】认为是y并到x上更新ans【x】。
2 字胡串
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
线段树加上循环,O(N^2logn)20分
O(N^2) 有50分 可以用st表或普通分治
分析:
由于最大的数或进去了
所以g(T)≥f(T)
找出所有值左右第一个大于它的位置,那么以它为最大值的区间就固定在这一段中。只要再
找出这个区间中左右第一个有一位不与最大值相同的值的位置,那么这个位置左边的所有位置都
可以与最大值右边的位置构成一个合法区间。右边也同理。
可以用单调栈找出左右第一个大于它的位置,再用 O(n log ai) 的时间处理出左右第一个有有
某一位为当前数超集的地方,然后就可以 O(n) 统计答案了,注意处理值相同的情况。
时间复杂度 O(n log ai)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define reg register int
using namespace std;
const int maxn=100010;
inline int rl()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return f*x;
}
int n,ans;
int f[maxn][30],g[maxn][30];
inline int askg(int i,int j)
{
int k=(int)((double)log(j-i+1)/log(2.0));
return g[i][k]|g[j+1-(1<<k)][k];
}
inline int askf(int i,int j)
{
int k=(int)((double)log(j-i+1)/log(2.0));
return max(f[i][k],f[j+1-(1<<k)][k]);
}
int main()
{
n=rl();
for(reg i=1;i<=n;++i) g[i][0]=f[i][0]=rl();
for(reg j=1;(1<<j)<=n;++j)
for(reg i=1;i+(1<<j)-1<=n;++i)
{
g[i][j]=g[i][j-1]|g[i+(1<<(j-1))][j-1];
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
for(reg i=1;i<=n;++i)
for(reg j=i;j<=n;++j) if(askg(i,j)>askf(i,j)) ans++;
printf("%d",ans);
return 0;
}
3 有环无向图
3.1 description
小 L 最近喜欢上了图论,他最近在着重研究一类图,叫做有环无向图。有环无向
点,m 条边组成的无向联通图。且图中 可能存在环,但不可能存在重边或自环。
小 L 对这个图上的最短路问题产生了兴趣,于是他花了 3 分钟时间学习了 SFP
得,这个最短路实在是太 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 。
3 有环无向图
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!
这道题用了令人自闭的奥妙技巧。
由于题目上的玄学要求,我们必须在原图的基础上修改点和边的关系,使得建一个新图,使得我们从长边遍历到短边时取自己,从短边遍历到长边的时候取长边。
令长边为edg[i],短边为edg[j]。
我们在建图的时候,把这个无向边拆成两条无向边,这就给我们解决这道题提供了出路:边权差分。
也就是说,在新图中,把每个点拆成度数个点,每条边之间链接,边权排序后,由短边点向大边点链接,链接边为二者边权差值,每条边都权值赋值为最短边,由长边到短边的权值链接为0.这样就能保证在原图中的遍历权值在题目要求下和新图中的普通遍历权值相同。
so here is the code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#define ll long long
#define rei register int
using namespace std;
const int maxn=2e5+10;
int n,m;
struct dnode
{
int num;ll vval;
};
bool operator <(const dnode &ddx,const dnode &ddy)
{
return ddx.vval>ddy.vval;
}
priority_queue<dnode> stac;
struct hiphop
{
int st;
int nd;
}res[maxn*3];
ll d[maxn*2];
struct node
{
int ver;
int nxt;
ll val;
}edge[maxn*2];
int head[maxn],ecnt=1;
bool cmp( hiphop x, hiphop y)
{
return x.nd<y.nd;
}
void add(int x,int y,ll z)
{
edge[++ecnt].ver=y;
edge[ecnt].nxt=head[x];
edge[ecnt].val=z;
head[x]=ecnt;
}
int vis[maxn*2];
struct heapnode
{
int to,next;
ll vale;
}e[maxn*5];
int h[maxn*3];
int cnt;
void Add(int x,int y,ll z)
{
e[++cnt].next=h[x];
h[x]=cnt;
e[cnt].to=y;
e[cnt].vale=z;
}
void Dijkstra()
{
for(rei i=2;i<=ecnt;++i)
d[i]=2e18;
for(rei i=head[1];i;i=edge[i].nxt)
{
d[i]=edge[i].val;
dnode u;u.num=i,u.vval=d[i];
stac.push(u);
}
while(stac.size())
{
if(vis[stac.top().num])
{stac.pop();continue;}
int x=stac.top().num;
vis[x]=1;stac.pop();
for(rei i=h[x];i;i=e[i].next)
{
if(vis[e[i].to]) continue;
if(d[e[i].to]>d[x]+e[i].vale)
{
d[e[i].to]=d[x]+e[i].vale;
dnode v;v.num=e[i].to;v.vval=d[e[i].to];
stac.push(v);
}
}
}
}
int tot;
int main()
{
int a,b;ll c;
cin>>n>>m;
for(rei i=1;i<=m;++i)
{
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c);add(b,a,c);
}
//cout<<"1"<<endl;
for(rei x=2;x<n;++x)
{
tot=0;
for(rei i=head[x];i;i=edge[i].nxt)
{
res[++tot].st=i,res[tot].nd=edge[i].val;
// cout<<edge[i].val<<endl;
}
sort(res+1,res+tot+1,cmp);
for(rei i=1;i<=tot;++i)
{
//cout<<"1"<<endl;
Add(res[i].st^1,res[i].st,res[i].nd);
if(i!=1) Add(res[i].st,res[i-1].st,0);
if(i!=tot) Add(res[i].st,res[i+1].st,res[i+1].nd-res[i].nd);
}
}
/*
for(int x=2;x<=ecnt;++x)
{
cout<<endl;
for(int i=h[x];i;i=e[i].next)
{
cout<<x<<" "<<e[i].to<<" "<<e[i].vale<<" ";
}
}
*/
// cout<<endl;
Dijkstra();
ll ans=2e18;
for(rei i=head[n];i;i=edge[i].nxt)
{
ans=min(ans,d[i^1]+edge[i].val);
}
cout<<ans<<endl;
return 0;
}