【NOIP模拟】T1+T2+T3

24 篇文章 0 订阅
19 篇文章 1 订阅

T1

解析:
  排列组合+树形DP。
  令 f [ i ] f[i] f[i]表示以 i i i为根的子树的合法种数,就有: f [ x ] = C s i z e [ x ] − 1 − s i z e [ y 1 ] s i z e [ y 1 ] ∗ f [ y 1 ] ∗ . . . . . . C s i z e [ x ] − 1 − s i z e [ y 1 ] − s i z e [ y 2 ] − . . . . . . s i z e [ y n ] s i z e [ y 1 ] ∗ f [ y n ] f[x]=C_{size[x]-1-size[y_1]}^{size[y_1]}*f[y_1]*......C_{size[x]-1-size[y_1]-size[y_2]-......size[y_n]}^{size[y_1]}*f[y_n] f[x]=Csize[x]1size[y1]size[y1]f[y1]......Csize[x]1size[y1]size[y2]......size[yn]size[y1]f[yn]
  意思就是每个分配给子树的方案数乘以这个子树的合法方案个数(感性理解)

代码:

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

const int mod=998244353;
const int Max=100010;
int n,m,s;
int size[Max],first[Max],f[Max],mul[Max],inv[Max];
struct shu{int to,next;}edge[Max<<1];

inline int get_int()
{
	int x=0,f=1;char c;
	for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
	if(c=='-') f=-1,c=getchar();
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	return x*f;
}

inline void build(int x,int y)
{
	edge[++s].next=first[x],first[x]=s,edge[s].to=y;
	edge[++s].next=first[y],first[y]=s,edge[s].to=x;
}
inline int mu(int a,int b){return a*b>=mod?a*b%mod:a*b;}
inline int ksm(int a,int b)
{
	int ans=1;
	while(b)
	{
	  if(b&1) ans=mu(ans,a);
	  b>>=1,a=mu(a,a);
	}
	return ans;
}
inline int C(int n,int m)
{
	if(n>=0&&m>=0&&n>=m) return mu(mul[n],mu(inv[m],inv[n-m]));
	return 0;
}
inline void dfs(int p,int fa)
{
	f[p]=1;
	for(int u=first[p];u;u=edge[u].next)
	{
	  int to=edge[u].to;
	  if(to==fa) continue;
	  dfs(to,p);
	  f[p]=mu(mu(f[p],f[to]),C(size[p]+size[to],size[to]));
	  size[p]+=size[to];
	}
	size[p]++;
}

signed main()
{
	n=get_int();
	mul[0]=1,inv[0]=1;
	for(int i=1;i<=n;i++) mul[i]=mu(mul[i-1],i),inv[i]=ksm(mul[i],mod-2);
	for(int i=1;i<n;i++)
	{
	  int x=get_int(),y=get_int();
	  build(x,y);
	}
	dfs(1,0);
	cout<<f[1];
	return 0;
}

T2

解析:
  分治。
  表示不想看单调栈的做法,况且分治速度吊打单调栈啊。
  很多区间计数类问题都可以用分治解决,这道题因为直接算不好算,考虑用总数减去不合法数,又因为满足 f [ i ] ≤ &ThickSpace; g [ i ] f[i]\leq\;g[i] f[i]g[i],所以我们只用算出满足 f [ i ] = g [ i ] f[i]=g[i] f[i]=g[i]的区间的个数即可。
  实现:

  1. 对于当前区间,取 m i d mid mid,枚举左端点,对于每个满足 m a x { n u m [ i ] . . . n u m [ m i d ] } = n u m [ i ∣ ∣ . . . ∣ n u m [ m i d ] max\{num[i]...num[mid]\}=num[i||...|num[mid] max{num[i]...num[mid]}=num[i...num[mid]
    的数,计算右端点最多能延申到哪。枚举右端点同理。
  2. 递归 ( l , m i d ) , ( m i d + 1 , r ) (l,mid),(mid+1,r) (l,mid),(mid+1,r)求解。

代码:

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

const int Max=1000010;
int n,m;
long long ans;
int num[Max],f[Max],g[Max];

inline int get_int()
{
	int x=0,f=1;char c;
	for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
	if(c=='-') f=-1,c=getchar();
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
	return x*f;
}

inline void init()
{
	n=get_int();
	for(int i=1;i<=n;i++) num[i]=get_int();
	ans=(long long)n*(n-1)/2;
}
inline void solve(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	f[mid]=g[mid]=num[mid];
	for(register int i=mid-1;i>=l;i--) f[i]=max(num[i],f[i+1]),g[i]=num[i]|g[i+1];
	f[mid+1]=g[mid+1]=num[mid+1];
	for(register int i=mid+2;i<=r;i++) f[i]=max(num[i],f[i-1]),g[i]=num[i]|g[i-1];
	int pos=mid;
	for(register int i=mid;i>=l;i--)
	{
	  if(g[i]!=f[i]) continue;
	  while(pos<r&&(g[pos+1]|g[i])==g[i]&&f[pos+1]<=f[i]) pos++;
	  ans-=pos-mid;
	}
	pos=mid+1;
	for(register int i=mid+1;i<=r;i++)
	{
	  if(g[i]!=f[i]) continue;
	  while(pos>l&&(g[pos-1]|g[i])==g[i]&&f[pos-1]<f[i]) pos--; //‘<’防止重复,仔细体会
	  ans-=mid-pos+1;
	}
	solve(l,mid),solve(mid+1,r);
}

int main()
{
	init();
	solve(1,n);
	cout<<ans;
	return 0;
}

T3

解析:
  最短路。
  先考虑这样建图。对于与每个点相连的边,枚举入边和出边,将入边和出点看成点,连接一条长度为 m a x ( l e n 1 , l e n 2 ) max(len_1,len_2) max(len1,len2)的单向边。跑最短路。
  思考这样建图是 O ( M 2 ) O(M^2) O(M2)的,考虑怎样优化。对于一个点,先把出入边按权值排序,入边连向他的反向边,权值为这条边原本的权值,每个反向边往下连长度为 0 0 0的边,向上连长度为两边差值的边。
  画个图就大概长这样:
在这里插入图片描述

代码:

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

const int Max=200010;
int n,m,size=1,tot;
long long ans=1e18,dis[Max<<1];
int first[Max];
bool vis[Max<<1];
struct shu{int to,next,len;}edge[Max<<1];
struct in{int id,val;}e[Max<<1];
vector<pair<int,int> >edg[Max<<1];

inline int get_int()
{
	int x=0,f=1;char c;
	for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
	if(c=='-') f=-1,c=getchar();
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	return x*f;
}

inline void build(int x,int y,int z)
{
	edge[++size].next=first[x],first[x]=size,edge[size].len=z;
	edge[++size].next=first[y],first[y]=size,edge[size].len=z;
}
inline bool comp(const in &a,const in &b){return a.val<b.val;}
inline void dijkstra()
{
	for(int i=2;i<=size;i++) dis[i]=1e18;
	priority_queue<pair<long long,int> >q;
	for(int u=first[1];u;u=edge[u].next) dis[u]=edge[u].len,q.push(make_pair(-edge[u].len,u));
	while(q.size())
	{
	  int p=q.top().second;q.pop();
	  if(vis[p]) continue;vis[p]=1;
	  for(int j=0;j<edg[p].size();j++)
	  {
	  	int to=edg[p][j].second;
	  	if(dis[to]>dis[p]+edg[p][j].first)
	  	{
	  	  dis[to]=dis[p]+edg[p][j].first;
	  	  q.push(make_pair(-dis[to],to));
	  	}
	  }
	}
}

inline void init()
{
	n=get_int(),m=get_int();
	for(int i=1;i<=m;i++)
	{
	  int x=get_int(),y=get_int(),z=get_int();
	  build(x,y,z);
	}
}
inline void pre()
{
	for(int i=2;i<n;i++)
	{
	  tot=0;
	  for(int u=first[i];u;u=edge[u].next) e[++tot].id=u,e[tot].val=edge[u].len;
	  sort(e+1,e+tot+1,comp);
	  for(int i=1;i<=tot;i++)
	  {
	    if(i!=1) edg[e[i].id].push_back(make_pair(0,e[i-1].id));
	    if(i!=tot) edg[e[i].id].push_back(make_pair(e[i+1].val-e[i].val,e[i+1].id));
	    edg[e[i].id^1].push_back(make_pair(e[i].val,e[i].id));
	  }
	}
}
inline void solve()
{
	dijkstra();
	for(int u=first[n];u;u=edge[u].next)
	{
	  int to=edge[u].to;
	  ans=min(ans,dis[u^1]+edge[u].len);
	}
	cout<<ans;
}

int main()
{
	init();
	pre();
	solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值