2018提高组模拟14

————————————————————————————20181030

还有10天就noip了,还有6天就要被半期摧残了……
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊~~~~~~~~~~~~~~~~~







还是写题解吧……~~





T1 排列树

【WOJ4163】

* _ *

排列组合

数学推理

从根往下搜,每一棵子树的根都记录一个siz大小。

每一棵子树的根,我们都要分配一个最小的值,

然后一个一个儿子地搜,ans乘上我们可以给这棵子树分配多少种不同的数,

再乘上子树内部的方案数。

因为阶乘取模之后再除答案就不对了,所以用逆元取模

  • a / b = a * bmod-2 % mod;【mod是个大质数】

一定要预处理阶乘和逆元!!!

#include<iostream>
#include<cstdio>
#include<cctype>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return f==1?x:-x;
}
#define mod 998244353
struct edge{
	int v,nxt;
}e[200005];
int first[100005],cnt=0;
inline void add(int u,int v){
	e[++cnt].v=v;
	e[cnt].nxt=first[u];first[u]=cnt;
}
int n,siz[100005],ans[100005];
int mul1[100005],mul2[100005];//阶乘   逆元  
int ksm(int x){
	int b=mod-2,res=1;
	while(b>0){
		if(b&1)res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		b>>=1;
	}
	return res;
}
int mul(int x,int y){
	return 1ll*mul1[x]*mul2[x-y]%mod*mul2[y]%mod;
}
void dfs(int x,int fa){
	for(int i=first[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,x);
		siz[x]+=siz[v];
	}
	ans[x]=1;
	int c=0;
	for(int i=first[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa)continue;
		ans[x]=1ll*ans[x]%mod*mul(siz[x]-c,siz[v])%mod*ans[v]%mod;
		c+=siz[v];
	}
	siz[x]++;
}
int main(){
	n=read();
	mul1[0]=mul2[0]=1;
	for(int i=1;i<=n;i++){
		mul1[i]=1ll*mul1[i-1]*i%mod;
		mul2[i]=ksm(mul1[i]);
	}
	for(int i=1;i<n;i++){
		int u,v;
		u=read();v=read();
		add(u,v);add(v,u);
	}
	dfs(1,0);
	printf("%d",ans[1]);
	return 0;
} 

T2 字胡串

【WOJ4164】

* _ *

单调栈

智商题

线性DP

对于每一个区间,最大值是二进制下0的位置其它数有至少一个为1,

那或起来就大于max,就是合法区间。

所以我们用单调栈找到每一个数左边离自己最近的严格比自己大的数和右边离自己最近的大于等于自己的数。

【注意一个是大于等于,一个是大于,避免计算重复】

再用线性DP找到左边离自己最近的或(|)自己更大的数和右边离自己最近的或(|)自己更大的数。

如果或在大的值里面就计算答案,注意重复。

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
int n,a[N];
stack<int>s;
int l[N],r[N];
//左边离自己最近的严格比自己大的数   右边离自己最近的大于等于自己的数
int dl[N],dr[N];
//左边离自己最近的或(|)自己更大的数   右边离自己最近的或(|)自己更大的数
int mx[50],mn[50];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){//L
		while(!s.empty()&&a[i]>=a[s.top()])s.pop();//一个>=一个> 避免重复 
		l[i]=(s.empty()?1:s.top()+1);
		s.push(i);
	}
	while(!s.empty())s.pop();
	for(int i=n;i>0;i--){//R
		while(!s.empty()&&a[i]>a[s.top()])s.pop();
		r[i]=(s.empty()?n:s.top()-1);
		s.push(i);
	}
	for(int i=1;i<=n;i++){//DL
		dl[i]=0;
		for(int j=0;(1<<j)<=a[i];j++){
			if(a[i]&(1<<j))mx[j]=max(mx[j],i);
			else dl[i]=max(dl[i],mx[j]);
		} 
	}
	fill(mn,mn+40,n+1);
	for(int i=n;i>0;i--){//DR
		dr[i]=n+1;
		for(int j=0;(1<<j)<=a[i];j++){
			if(a[i]&(1<<j))mn[j]=min(mn[j],i);
			else dr[i]=min(dr[i],mn[j]);
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++){//ANS
		if(dl[i]>=l[i])ans+=1ll*(dl[i]-l[i]+1)*(r[i]-i+1);
		if(dr[i]<=r[i])ans+=1ll*(r[i]-dr[i]+1)*(i-l[i]+1);
		if(dl[i]>=l[i]&&dr[i]<=r[i])ans-=1ll*(dl[i]-l[i]+1)*(r[i]-dr[i]+1);
	}
	printf("%lld",ans);
	return 0;
}

T3 有环无向图

【WOJ4165】

* _ *

最短路

智商题

struct E{
	int x,y;
	E(){}
	E(int a,int b):x(a),y(b);
};

a,b可换

可以直接用

a=E(e,f);

赋值,相当于

a.x=e;a.y=f;

先将边从小到大排序,相邻的两条边(把边当成点)建边

根据题中要求,

向比自己的边连一条0的边,向比自己大的边连一条Wj+1-Wj的边,

再向自己的反向边连一条Wj的边,避免算出来全是差值,

再特殊处理起点和终点。

这道题有点绕,我现在都还是晕乎乎的~~

#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define inf 1000000000000000000
int n,m,t=1;
priority_queue<pair<long long,int> >q;
vector<pair<int,int> >vec[N];//
struct edge{
	int id,val;
	edge(){}//
	edge(int id,int val):id(id),val(val){}
	friend inline bool operator <(const edge &a,const edge &b){
        return a.val<b.val;
    }
}a[N];
struct E{
	int u,v,w,nxt;
}e[N];
int first[N],cnt=1;
inline void add(int u,int v,int w){
	e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;
	e[cnt].nxt=first[u];first[u]=cnt;
}
long long dis[N];
bool vis[N];
void dijkstra(){
	for(int i=2;i<=cnt;i++)dis[i]=inf;
	for(int i=first[1];i;i=e[i].nxt){
		dis[i]=e[i].w;
		q.push(make_pair(-dis[i],i));
	}
	while(!q.empty()){
		int x=q.top().second;q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=0;i<vec[x].size();++i){
			int v=vec[x][i].first,w=vec[x][i].second;
			if(dis[v]>dis[x]+w){
				dis[v]=dis[x]+w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	for(int i=1;i<=n;i++){//
		int num=0;
		for(int j=first[i];j;j=e[j].nxt)
			a[++num]=edge(j,e[j].w);
		sort(a+1,a+num+1);
		for(int j=1;j<=num;j++){
			if(j!=1)vec[a[j].id].push_back(make_pair(a[j-1].id,0));
			if(j!=num)vec[a[j].id].push_back(make_pair(a[j+1].id,a[j+1].val-a[j].val));
			vec[a[j].id^1].push_back(make_pair(a[j].id,a[j].val));//连自己? 
		}
	}
	dijkstra();
	long long ans=inf;
	for(int i=first[n];i;i=e[i].nxt)
		ans=min(ans,dis[i^1]+e[i].w);
	printf("%lld",ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值