Logo __vector__ 的博客

博客

标签
暂无

P2573滑雪题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-07-03 15:59:26

这道题需要注意的是,数据范围太阴间,需要开long long才行,数值范围也要搞好。

#define maxn 100002
#define maxm 2000002
typedef long long ll;

这道题可以用广搜+最小生成树+并查集快速解决。其中要使用结构体存图,赋值给两个数组,一个用于存储原始数据,另一个用于存储BFS处理后的数据

struct EDGE\/\/重温邻接表
{
	ll u,v,w,next;
}edge[maxm],edge_fb[maxm];

最初图输入时,因为只能从一个点 到更低或一样高的点,所以加一个判断,如果头点edge[i].u 大于或等于尾点edge[i].to那么建立从edge[i].u 到 edge[i].to的边,然后再判断(千万不要加else,直接判断,我在这里挂了好几个测试点),如果尾点edge[i].to 大于或等于头点edge[i].u那么建立从edge[i].to 到edge[i].u的边。

cin >> n >> m;
for(int i = 1;i <= n;i++)
{
	cin >> h[i];
}
for(int i = 1;i <= n;i++)
{
	f[i]=i;
}
for(int i = 1; i<= m;i++)
{
	int u,v,w;
	cin >> u >> v >> w;
	if(h[u] >= h[v])add_edge(u,v,w);
	if(h[u] <= h[v]) add_edge(v,u,w);\/\/只能是从高的往低的建边
}

然后要枚举每一个能走到的点和边,并存入第二个结构体数组,这就要用到搜索了,既可以用广搜也可以用深搜,本蒟蒻只能用广搜来AC,深搜暂时只有80分,所以就使用广搜了。

inline void bfs(ll u)\/\/BFS函数,谁都应该会吧
{
	q.push(u);
	vis[u]=1;
	while(!q.empty())
	{
		int tmp = q.front();\/\/存储状态
		q.pop();
		for(int i = head[tmp];i != 0;i = edge[i].next)
		{
			edge_fb[++edge_sum].u=tmp;\/\/保存状态
			edge_fb[edge_sum].v = edge[i].v;
			edge_fb[edge_sum].w = edge[i].w;
			int tmp2 = edge[i].v;
			if(!vis[tmp2])\/\/如果没遍历这个点
			{
				q.push(tmp2);
				node_sum++;
				vis[tmp2]=1;
			}
		}
	}
}

然后要对BFS处理后的数据进行一波排序,要将边的长度从小到大排,将高度从高到低排,因为kruskal尽量求最小值,按顺序来依次枚举,逐渐增大,才能保证数据最小,所以从小到大排。而只能从高到低,所以从高到低排。

ll cmp(EDGE a,EDGE b)
{
	return h[a.v]!=h[b.v]?h[a.v]>h[b.v]:a.w<b.w;
}
sort(edge_fb+1,edge_fb+edge_sum+1,cmp);

然后就是最小生成树kruskal+并查集(本蒟蒻只会kruskal),这个就不用说了,神仙们一定都会

inline void kruscal()
{
	for(int i = 1;i <= edge_sum;i++)
	{
		int xx = find(edge_fb[i].u);
		int yy = find(edge_fb[i].v);
		if(xx!=yy)
		{
			\/\/cout << "ok: " << edge_fb[i].w << endl;
			f[xx]=yy;
			ans+=edge_fb[i].w;
		}
	}
}

下面附上完整代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<utility>
#include<deque>
#include<ctime>
#include<sstream>
#include<list>
#include<bitset>
using namespace std;
#define maxn 100002
#define maxm 2000002
typedef long long ll;
ll n,m,f[maxn],h[maxn],cnt,head[maxn],edge_sum,node_sum=1,ans;
bool vis[maxn];
queue<int> q;
struct EDGE\/\/重温邻接表(有向图)
{
	ll u,v,w,next;
}edge[maxm],edge_fb[maxm];
void add_edge(int u,int v,int w)\/\/普通的加边函数
{
	edge[++cnt].v = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u]=cnt;
}
inline void bfs(ll u)\/\/BFS函数,谁都应该会吧
{
	q.push(u);
	vis[u]=1;
	while(!q.empty())
	{
		int tmp = q.front();\/\/存储状态
		q.pop();
		for(int i = head[tmp];i != 0;i = edge[i].next)
		{
			edge_fb[++edge_sum].u=tmp;\/\/保存状态
			edge_fb[edge_sum].v = edge[i].v;
			edge_fb[edge_sum].w = edge[i].w;
			int tmp2 = edge[i].v;
			if(!vis[tmp2])\/\/如果没遍历这个点
			{
				q.push(tmp2);
				node_sum++;
				vis[tmp2]=1;
			}
		}
	}
}
ll cmp(EDGE a,EDGE b)
{
	return h[a.v]!=h[b.v]?h[a.v]>h[b.v]:a.w<b.w;
}\/\/要将高度从高到低排列,长度从短到长排列,方便最小生成树
ll find(int to)
{
	return f[to]==to?to:f[to]=find(f[to]);
}
inline void kruscal()
{
	for(int i = 1;i <= edge_sum;i++)
	{
		int xx = find(edge_fb[i].u);
		int yy = find(edge_fb[i].v);
		if(xx!=yy)
		{
			\/\/cout << "ok: " << edge_fb[i].w << endl;
			f[xx]=yy;
			ans+=edge_fb[i].w;
		}
	}
}
int main()
{
	cin >> n >> m;
	for(int i = 1;i <= n;i++)
	{
		cin >> h[i];
	}
	for(int i = 1;i <= n;i++)
	{
		f[i]=i;
	}
	for(int i = 1; i<= m;i++)
	{
		int u,v,w;
		cin >> u >> v >> w;
		if(h[u] >= h[v])add_edge(u,v,w);
		if(h[u] <= h[v]) add_edge(v,u,w);\/\/只能是从高的往低的建边
	}
	bfs(1);\/\/所有能搜到的点就是最多能到达点数
	sort(edge_fb+1,edge_fb+edge_sum+1,cmp);
	kruscal();
	cout << node_sum << " " << ans << endl;
    return 0;
}

P5683题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-07-16 19:57:33

题目说明

给定m条边,问从首都到城市1和城市2在限定时间内最多拆除道路数。如果无解输出-1。

题目解法

这道题可以反过来想,可以先求出至少需要多少条路。这就成了一个最短路问题,最大的麻烦是两条路可能会重合,这里就需要使用枚举每一个点作为中间点mid。首都到城市1总距离就等于(1 -> mid) + (mid -> s1),到城市二则为(1 -> mid) + (mid -> s2),然后判断这是不是所有结果中最小的,这样,最难得部分就处理完了。接下来最少道路数使用 dij 或 bfs 求即可。

Dijkstra:

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
int dis[100005],dis2[100005],dis3[100005],head[100005],cnt,n,m,s1,t1,s2,t2,inf=0x3f3f3f3f;
bool vis[100005];
struct EDGE
{
    int to,nxt,val;
}edge[100005];
void add_edge(int u,int t,int v=1)
{
    edge[++cnt].to=t;
    edge[cnt].val=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}
void dij(int start)\/\/可爱的dij
{
    memset(vis,0,sizeof(vis));
    memset(dis,inf,sizeof(dis));
    priority_queue< pair<int,int> > que;
    que.push(make_pair(0,start));\/\/dij永远配小根堆
    dis[start]=0;
    while(!que.empty())
    {
        int u = que.top().second;
        que.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i = head[u];i;i=edge[i].nxt)
        {
            int to = edge[i].to;
            if(dis[to] > dis[u]+edge[i].val)\/\/经典的松弛
            {
                dis[to]=dis[u]+edge[i].val;
                que.push(make_pair(-dis[to],to));\/\/小根堆永远是符号加入
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i = 1;i <= m;i++)
    {
        int u,t;
        cin>>u>>t;
        add_edge(u,t);
        add_edge(t,u);
    }
    cin>>s1>>t1>>s2>>t2;
    dij(s1);
    for(int i = 1;i <= n;i++)dis2[i]=dis[i];
    dij(s2);
    for(int i = 1;i <= n;i++)dis3[i]=dis[i];
    dij(1);\/\/从起始点到每个点的最少需要道路数
    int ans=inf;
    for(int i = 1;i <= n;i++)
    {
        if(dis[i]+dis2[i] <= t1&&dis[i] + dis3[i] <= t2)\/\/如果满足要求
        {
            ans=min(ans,dis[i]+dis2[i]+dis3[i]);
        }
    }
    if(ans!=inf)
    {
        cout<<m-ans<<endl;\/\/总道路数减去需要道路数就是拆除道路数
        return 0;
    }
    cout << -1<<endl;
    return 0;
}

BFS:


#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
int dis[100005],dis2[100005],dis3[100005],head[100005],cnt,n,m,s1,t1,s2,t2,inf=0x3f3f3f3f;
bool vis[100005];
struct EDGE
{
    int to,nxt,val;
}edge[100005];
void add_edge(int u,int t,int v=1)
{
    edge[++cnt].to=t;
    edge[cnt].val=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}
void bfs(int start)\/\/可爱的BFS
{
    memset(vis,0,sizeof(vis));
    memset(dis,inf,sizeof(dis));
    queue<int> que;
    que.push(start);\/\/即使是BFS,我也要用它 
    dis[start]=0;
    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i = head[u];i;i=edge[i].nxt)
        {
            int to = edge[i].to;
            if(dis[to]>dis[u]+1)
        	{
        		dis[to]=dis[u]+1;
			}
            que.push(to);
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i = 1;i <= m;i++)
    {
        int u,t;
        cin>>u>>t; 
        add_edge(u,t);
        add_edge(t,u);
    }
    cin>>s1>>t1>>s2>>t2;
    \/*千万不要用指针这个sb,它坑死我了*\/
    bfs(s1);
    for(int i = 1;i <= n;i++)dis2[i]=dis[i];
    bfs(s2);
    for(int i = 1;i <= n;i++)dis3[i]=dis[i];
    bfs(1);\/\/从起始点到每个点的最少需要道路数
    int ans=inf;
    for(int i = 1;i <= n;i++)
    {
        if(dis[i]+dis2[i] <= t1&&dis[i] + dis3[i] <= t2)\/\/如果满足要求
        {
            ans=min(ans,dis[i]+dis2[i]+dis3[i]);
        }
    }
    if(ans!=inf)
    {
        cout<<m-ans<<endl;\/\/总道路数减去需要道路数就是拆除道路数
        return 0;
    }
    cout << -1<<endl;
    return 0;
}

到此,本题就做完了!

25题题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-11 21:31:34

P3958 [NOIP2017 提高组] 奶酪

难度:$\color{orange}\text{普及/提高-}$

$\color{green}\text{AC算法:dfs}$

题目分析:

数据范围较小,可以dfs所有通向下面的点,看看是否可以到达上面即可

解题流程:

一、预处理所有通向上面的洞,使用一个map容器来记录某个洞是不是通向上面的洞。
二、从所有通向下面的点出发dfs,如果最终到达通向上面的洞,则输出Yes,否则输出No,此部分代码:

bool flag = 0;
for (register auto i = 1;i<=n; i++)
{
\/\/    memset(vis, 0, sizeof(vis));
     if(ball[i].z<=r){
              if(dfs(i)==1){
                printf("Yes\n");
                flag = 1;
                break;
              }
		}
      if (!flag)
      {
         printf("No\n");
      }
}

总体AC代码就是:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
const int maxn = 1005;
#define int long long
struct Ball
{
    int x, y, z;

} ball[maxn];
inline double dis(Ball a,Ball b)
{
    register int _1 = a.x - b.x;
    register int _2 = a.y - b.y;
    register int _3 = a.z - b.z;
    return sqrt((_1 * _1) + (_2 * _2) + (_3 * _3));
}
inline int read()
{
    int x=0,f=1;
    char ch = getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            f = -1;
        ch = getchar();
    }
    while(isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
int t,n,h,r;
bool vis[maxn];
map<int, bool> im;
bool dfs(int x)
{
    if(im[x]!=0)
    {
        return 1;
    }
    vis[x] = 1;
    for (register int i = 1; i <= n;i++)
    {
        if(i==x)continue;
        if(dis(ball[i],ball[x])>(r<<1))
            continue;
        register int to = i;
        if(!vis[to])
        {
            if(dfs(to)==1)
            {
                return 1;
            }
        }
    }
    return 0;
}
signed main()
{
    t = read();
    map<int, bool> imt;\/\/是不是与上面相交的洞
    while(t--)
    {
        memset(vis, 0, sizeof(vis));
        imt.clear();
        n = read(), h = read(), r = read();
        for (register int i = 1; i <= n;i++)
        {
            ball[i].x = read();
            ball[i].y = read();
            ball[i].z = read();
            if(h-ball[i].z<=r)
            {
                imt[i] = 1;
            }
        }
        im = imt;
        bool flag = 0;
        for (register auto i = 1;i<=n; i++)
        {
        \/\/    memset(vis, 0, sizeof(vis));
            if(ball[i].z<=r){
            if(dfs(i)==1){
                printf("Yes\n");
                flag = 1;
                break;
            }}
        }
        if (!flag)
        {
            printf("No\n");
        }
    }
    return 0;
}

[NOIP2013 提高组] 货车运输 | P1967

难度:$\color{blue}\text{提高+/省选-}$

$\color{green}\text{AC算法:lca,kruscal}$

题目分析:

可以发现,对于图中一个点到另一个点的最大限重,就是两个点之间最小边权最大的路径,其他的路径都不会走。此处例如样例中的一个例子:
1$\rightarrow$3有两条路径,一个是 1$\rightarrow$2$\rightarrow$3;另一个是1$\rightarrow$3。第一条路径限重3,第二条路径限重1。显然,不会走第二条路径。
如何解决这个问题我先想到了网络流的最大流,但是显然在这个数据范围下AC不了。可以构建一个最大生成树来存储要走的路径,使用kruscal算法即可。 接下来使用lca来查询路径即可(可以理解为将根节点作为中点)。

解体流程:

一、使用前向星存图
二、构建最大生成树
三、处理每个节点的深度,父子信息
四、更新每个节点的祖先、关系
五、lca查询

注意:

预处理 $lca$ 时一定要这样写,不然会出现处理当前节点时祖先未处理的情况:

for(int i=0; i<20; i++)
{
    for(int j=1; j<=n; j++)
    {
        fa[j][i+1]=fa[fa[j][i]][i]; 
        w[j][i+1]=min(w[j][i], w[fa[j][i]][i]);
    }
}

AC代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define int long long
const int inf=0xffffffffff; 
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const signed maxn=1e4+5;
const signed maxm=5e4+5;
int tot,tot2,n,m,q,f[maxn]\/*骞舵煡闆?*\/,fa[maxn][21*2]\/*lca*\/,dep[maxn];
int w[maxn][21*2]; 
bool vis[maxn];
int head[maxn];
struct EDGE
{
    int u,to,val;
}edge[maxm];
inline void add_edge(int u,int to,int val)
{
    edge[++tot].to=to;
    edge[tot].val=val;
    edge[tot].u=u;
}
struct EDGE2
{
    int to,nxt,val;
}edge2[maxm];
inline void add_edge2(int u,int to,int val)
{
    edge2[++tot2].to=to;
    edge2[tot2].val=val;
    edge2[tot2].nxt=head[u];
    head[u]=tot2;
}
int find(int x)
{
    return x==f[x]?x:f[x]=find(f[x]);
}
bool cmp(EDGE a,EDGE b)
{
    return a.val>b.val;
}
inline void kruscal()
{
    for(register int i=1;i<=m;i++)
    {
        if(find(edge[i].u)!=find(edge[i].to))
        {
            f[find(edge[i].u)]=find(edge[i].to);
           \/\/ cout<<"u: "<<edge[i].u<<"  to: "<<edge[i].to<<endl;
            add_edge2(edge[i].u,edge[i].to,edge[i].val);
            add_edge2(edge[i].to,edge[i].u,edge[i].val);
        }
    }
}
void dfs(int u,int father)
{
   \/\/ cout<<"node: "<<u<<endl;
    dep[u]=dep[father]+1;
    vis[u]=1;
    for(register int i=head[u];i;i=edge2[i].nxt)
    {
    	int to=edge2[i].to;
    	if(vis[to])continue;
    	fa[to][0]=u;
    	w[to][0]=edge2[i].val;
    	dfs(to,u); 
	}
}
inline int lca(int x,int y)
{
	if(find(x)!=find(y))return -1;
	int ans=inf;
	if(dep[x]<dep[y])swap(x,y);
	for(register int i=20;i>=0;i--)
	{
		if(dep[fa[x][i]]>=dep[y])
		{
			ans=min(ans,w[x][i]);
		    x=fa[x][i];
		}
	}
	if(x==y)return ans;
	for(register int i=20;i>=0;i--)
	{
		if(fa[x][i]!=fa[y][i])
        {
            ans=min(ans,w[x][i]);
		    ans=min(ans,w[y][i]);
		    x=fa[x][i];
		    y=fa[y][i];
        }
	}
	ans=min(ans,min(w[x][0],w[y][0]));
	return ans;
}
signed main()
{
    int x,y,z;
    n=read();
    m=read();
    for(register int i=1;i<=m;i++)
    {
        x=read();
        y=read();
        z=read();
        add_edge(x,y,z);
    }
    sort(edge+1,edge+m+1,cmp);
    for(register int i=1;i<=n;i++)f[i]=i;
    kruscal();
    for(register int i=1;i<=n;i++)
    {
    	if(vis[i])continue;
    	dfs(i,0);
    	w[i][0]=inf;
    	fa[i][0]=i;
	}
	for(int i=0; i<20; i++)
    {
        for(int j=1; j<=n; j++)
        {
            fa[j][i+1]=fa[fa[j][i]][i]; 
            w[j][i+1]=min(w[j][i], w[fa[j][i]][i]);
        }
    }
        
	q=read();
	for(register int i=1;i<=q;i++)
	{
		x=read();
		y=read();
		printf("%lld\n",lca(x,y));
	}
    return 0;
}

P3959 [NOIP2017 提高组] 宝藏

AC算法:dfs+状压

分析:

可以知道,每个点必然要算一次深度以及路径。所以可以通过已经更新的点u来推导未更新的点to,所以需要dfs。具体更新过程如下:
$Dep[to]=dep[u]+1$
F[to][当前点集][当前深度+1]=中间答案$cur+dis(u,to) * dep[u]$ 那么点集如何表示呢?可以使用状压来解决,定义一个整数$dj$,初始默认为$1<<(i-1)$,这样它的第$i-1$位就是1,表示$i$存在于这个点集里。如果想要向其加入一个数$s$,就表示为: $dj |=(s-1)$,判断是否在点集里可以用if(($1<<(s-1)&dj$))来判断。
那么如何设状态呢?
可以用三维数组f,分别为当前点,当前点集,当前深度。
最后,就是边界条件的处理:
可以发现,每个点都更新完之后,肯定是dj的二进制每一位都是1。这个恰好等于(1<<n)-1,所以如果dj==(1<<n)-1,那么更新答案退出。
调了一个下午的AC代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n,m;
const int inf=0x3f3f3f3f;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int maxn=15;
int imap[maxn][maxn];
int ndep[maxn];\/\/记录从起点到路径开始
int ans=inf;
int f[maxn][8193][maxn];\/\/当前点,点集,深度
inline int min(int a,int b)
{
    return a<b?a:b;
}
void dfs(int dj\/*点集*\/,int dep\/*深度*\/,int cur\/*当前解*\/)
{
    if(cur>=ans)return;
    if(dj==((1<<n)-1))\/\/边界条件
    {
        ans = cur;
        return;
    }
    for(register int u=1;u<=n;u++)
    {
        if(!(1<<(u-1)&dj))continue;\/\/不在点集里,不能通过它更新其他点
        for(register int to=1;to<=n;to++)
        {
            if(!(1<<(to-1)&dj)&&imap[u][to]<inf)
            {
                \/\/如果需要更新,且可以通过u更新
                if(f[to][dj|(1<<(to-1))][dep+1]<=cur+imap[u][to]*ndep[u])
                {\/\/如果下一步不是最优解,剪枝
                    continue;
                }
                f[to][dj|(1<<(to-1))][dep+1]=cur+imap[u][to]*ndep[u];
                ndep[to]=ndep[u]+1;
                dfs(1<<to-1|dj,dep+1,f[to][1<<(to-1)|dj][dep+1]);
            }
        }
    }
}
int main()
{

    memset(imap, 0x3f3f3f3f, sizeof(imap));
    n = read();
    m=read();
    int x,y,v;
    for(register int i=1;i<=m;i++)
    {
        x=read();
        y=read();
        v=read();
        imap[x][y]=imap[y][x]=min(imap[x][y],v);
    }
    for(register int i=1;i<=n;i++)
    {
        memset(ndep,0,sizeof(ndep));
        memset(f,0x3f3f3f3f,sizeof(f));
        ndep[i] = 1;
        dfs(1<<(i-1),0,0);
    }
    printf("%d",ans);
    return 0;
}

如果是纯dfs呢,可以加两个剪枝就过了。第一个当然就 if(cur>ans)return;。这样就能获得70分的好成绩。第二个可以仿照状压dp的思路,定义一个三维数组f,代表当前点,当前深度,当前点数量(其实就相当于状压做法的点集),加上这句话:

if(f[to][sd[u]+1][tot+1]<cur+sd[u]*imap[u][to])
                        continue;

然后将更新点的部分改成这个:

  f[to][sd[u] + 1][tot+1] = cur + sd[u] * imap[u][to];
  sd[to]=sd[u]+1;
  cur+=sd[u]*imap[u][to];
  sd[to]=sd[u]+1;
  tot++;
  dfs(to,step+1,cur);
  tot--;
  cur-=sd[u]*imap[u][to];
  sd[to]=0;

但是真的这样就行了吗?如果你交了这份代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n,m;
const int inf=0x3f3f3f3f;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int maxn=13;
int imap[maxn][maxn];
int sd[maxn];\/\/记录从起点到路径开始
int ans=inf;
int tot = 1;
int f[maxn] \/*第几个点*\/[maxn]\/*深度*\/[maxn]\/*tot*\/;
inline void memset()
{
    for(int i=0;i<13;i++)
        for(int j=0;j<13;j++)imap[i][j]=inf;
}
inline void memset2()
{
    for(int j=0;j<13;j++)sd[j]=0;
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
void dfs(int x,int step,int cur)
{
    if(cur>ans)return;
 \/\/   cout<<"step: "<<step<<endl;
    if(step==n)
    {
        ans=min(ans,cur);
   \/\/     cout<<"cur: "<<cur<<endl;
        return;
    }
    for(register int to=1;to<=n;to++)
    {
        if(!sd[to])\/\/ mod zgc
        {
            for(register int u=1;u<=n;u++)
            {
                if(imap[u][to]!=inf&&sd[u]!=0)
                {
                    if(f[to][sd[u]+1][tot+1]<cur+sd[u]*imap[u][to])
                        continue;
                    if(cur+sd[u]*imap[u][to]>ans)continue;
                    f[to][sd[u] + 1][tot+1] = cur + sd[u] * imap[u][to];
                    sd[to]=sd[u]+1;
                    cur+=sd[u]*imap[u][to];
                    sd[to]=sd[u]+1;
                    tot++;
                    dfs(to,step+1,cur);
                    tot--;
                    cur-=sd[u]*imap[u][to];
                    sd[to]=0;
                }
            }
        }
    }
}
int main()
{
    memset();
    n=read();
    m=read();
    int x,y,v;
    for(register int i=1;i<=m;i++)
    {
        x=read();
        y=read();
        v=read();
        imap[x][y]=imap[y][x]=min(imap[x][y],v);
    }
    for(register int i=1;i<=n;i++)
    {
        tot = 1;
        memset(f, 0x3f, sizeof(f));
        sd[i]=1;
        dfs(i,1,0);
        sd[i]=0;
    }
    printf("%d",ans);
    return 0;
}

将会收获:95分。
计算一下可以发现原来的不加第二个剪枝的算法可以在n=10的时候挺住。所以可以在n>10时使用剪枝,n<=10时使用原来的搜索。提交这份代码就可以AC。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n,m;
const int inf=0x3f3f3f3f;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int maxn=13;
bool vis[maxn][maxn];
int imap[maxn][maxn];
int sd[maxn];\/\/记录从起点到路径开始
int ans=inf;
int tot = 1;
int f[maxn] \/*第几个点*\/[maxn]\/*深度*\/[maxn]\/*tot*\/;
inline void memset()
{
    for(int i=0;i<13;i++)
        for(int j=0;j<13;j++)imap[i][j]=inf;
}
inline void memset2()
{
    for(int j=0;j<13;j++)sd[j]=0;
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
void dfs(int x,int step,int cur)
{
    if(cur>ans)return;
 \/\/   cout<<"step: "<<step<<endl;
    if(step==n)
    {
        ans=min(ans,cur);
   \/\/     cout<<"cur: "<<cur<<endl;
        return;
    }
    for(register int to=1;to<=n;to++)
    {
        if(!sd[to])\/\/ mod zgc
        {
            for(register int u=1;u<=n;u++)
            {
                if(imap[u][to]!=inf&&sd[u]!=0)
                {
                    if(f[to][sd[u]+1][tot+1]<cur+sd[u]*imap[u][to])
                        continue;
                    if(cur+sd[u]*imap[u][to]>ans)continue;
                    f[to][sd[u] + 1][tot+1] = cur + sd[u] * imap[u][to];
                    sd[to]=sd[u]+1;
                    cur+=sd[u]*imap[u][to];
                    sd[to]=sd[u]+1;
                    tot++;
                    dfs(to,step+1,cur);
                    tot--;
                    cur-=sd[u]*imap[u][to];
                    sd[to]=0;
                }
            }
        }
    }
}
void dfs_henman(int x,int step,int cur)
{
    if(cur>ans)return;
 
    if(step==n)
    {
        ans=min(ans,cur);
        return;
    }
    for(register int to=1;to<=n;to++)
    {
        if(!sd[to])\/\/ mod zgc
        {
            for(register int u=1;u<=n;u++)
            {
                if(imap[u][to]!=inf&&sd[u]!=0)
                {
                    if(cur+sd[u]*imap[u][to]>ans)continue;
                    sd[to]=sd[u]+1;
                    cur+=sd[u]*imap[u][to];
                    sd[to]=sd[u]+1;
                    dfs_henman(to,step+1,cur);
                    cur-=sd[u]*imap[u][to];
                    sd[to]=0;
                }
            }
        }
    }
}
int main()
{
    memset();
    n=read();
    m=read();
    int x,y,v;
    for(register int i=1;i<=m;i++)
    {
        x=read();
        y=read();
        v=read();
        imap[x][y]=imap[y][x]=min(imap[x][y],v);
    }
    if(n>10)
    {
        for(register int i=1;i<=n;i++)
        {
            tot = 1;
            memset(f, 0x3f, sizeof(f));
            sd[i]=1;
            dfs(i,1,0);
            sd[i]=0;
        }
    }
    if(n<=10)
    {
        for(register int i=1;i<=n;i++)
        {
            sd[i]=1;
            dfs_henman(i,1,0);
            sd[i]=0;
        }
    }
    printf("%d",ans);
    return 0;
}

此时对比一下:dfs解法提交记录状态压缩解法提交记录,dfs用了102ms,状压用了405ms,dfs比状压快了5倍!!!!!!

CF1137C Museums Tour

$\color{green}\text{AC算法:}$tarjan缩点+dfn最长路

难度:$\color{purple}\text{省选/NOI-}$

题目内容:

有 $n$ 个城市,$m$ 条边,每周有 $d$ 天,每个城市都会在每周特定的时间开放博物馆,旅行者可以无限走,也可以随时停下来。求参观的不同博物馆数量尽量多。

题目分析

难点就在于每周都有一个轮回,每个城市都在特定时间开放,可以看作题目提供的图 $G1$ 中的点有时候有点权有时没点权。所以可以将每个点拆成 $d$ 个点,每个点 $u$ 连接明天的点 $v$,如果当前 $u$ 已经是第 $d$ 天,那么连接第 $1$ 天的 $v$。这样每条边都有了明确的点权。然后就是如何处理题目中的环,可以用 tarjan 算法将每个环缩成一个强连通分量,每个强连通分量(新的点)的点权就是其中所有原来点权的和。最后将所有强连通分量建边就构成了一张新的图 $G2$,在 $G2$中用 dfs 求最长路即可。

背包dp复习

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-23 21:07:16

P1048 [NOIP2005 普及组] 采药

难度:$\color{orange}\text{普及-}$

$\color{green}\text{AC算法:01背包}$

题目分析:

题目转化:有 $m$ 个物品,价值为 $w[i]$,大小为 $t[i]$。总容量大小为 $t$.
可以设一个状态 $dpm[i][j]$,代表当前已经确定 $i$ 个物品且当前空间为 $j$ 时的最大价值。那么转移方程显然为:

dpm[i][j]=max(dpm[i-1][j],dpm[i-1][j-ti[i]]+w[i]);

$dpm[i-1][j]$ 代表之前已经确定了 $i-1$ 个物品,空间为 $j$。且不放置当前物品时的最大价值。$dp[i-1][j-ti[i]]$ 代表之前已经确定了 $i-1$ 个物品,放置了当前物品的情况。既然放置了当前物品,那么之前的空间肯定是 $j-ti[i]$ 。(此时可以发现前面都是i-1,所以可以直接砍掉第一个数组维度,进行滚动数组优化)

放上$\color{green}\text{AC}$代码:

#include <bits\/stdc++.h>
using namespace std;
#define reg register 
int t,m;
int w[1005];
int ti[1005];
int dpm[1005];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
inline void dp()
{
    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=ti[i];j--)
        {
            \/\/如果不使用滚动数组,那么就是:
            \/\/dpm[i][j]=max(dpm[i-1][j],dpm[i-1][j-ti[i]]+w[i]);
            \/\/意思是选择了i个,且当前容量为j的最大价值
            dpm[j]=max(dpm[j],dpm[j-ti[i]]+w[i]);
        }
    }
}
int main()
{
    t=read();
    m=read();
    for(reg int i=1;i<=m;i++)
    {
        ti[i]=read();
        w[i]=read();
    }
    dp();
    printf("%d",dpm[t]);
    return 0;
}

P1616 疯狂的采药

难度:$\color{orange}\text{普及-}$

$\color{green}\text{AC算法:完全背包}$

题目分析:

本题与01背包类似,只是变成了每种有无限个。但是此时状态转移方程却还是原来那个:$dpm[j]=max(dpm[j],dpm[j-ti[i]]$,此时仅仅是枚举空间大小的顺序和01背包相反,因为从小到大枚举时,例如首先更新了$dpm[j-3ti[i]]$的最大价值,然后空间向上枚举了ti[i]后,就相当于使用$dpm[j-3ti[i]]$去更新$dpm[j-3ti[i]+ti[i]]$,也就是$dpm[j-2ti[i]]$,以此类推,直到更新$dpm[j]$的最大价值。(此时和01背包一样使用了滚动数组优化)

放上$\color{green}\text{AC}$代码:

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define reg register 
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int maxn=1e4+5;
const int maxdp=1e7+5;
int t,m;
int ti[maxn];
int w[maxn];
ll dpm[maxdp];
inline void dp()
{
    for(reg int i=1;i<=m;i++)
    {
        for(int j=ti[i];j<=t;j++)
        {
            dpm[j]=max(dpm[j],dpm[j-ti[i]]+w[i]);
        }
    }
}
signed main()
{
    t=read();
    m=read();
    for(reg int i=1;i<=m;i++)
    {
        ti[i]=read();
        w[i]=read();
    }
    dp();
    printf("%lld",dpm[t]);
    return 0;
}

P1776 宝物筛

难度:$\color{green}\text{ 普及+/提高}$

$\color{green}\text{AC算法:多重背包}$

题目分析:

题目转化:
给一个大小为 $W$的背包,共有 $n$ 种物品,每种有 $m[i]$ 件,价值为 $v[i]$,大小为 $w[i]$。问能获得的最大价值。
可以发现,它与01背包不同之处在于它一个物品有多个。这样就可以把第 $m[i]$ 种物品拆分成 $m[i]$个物品,枚举k为$(1 \rightarrow\ m[i])$即可。那么转移方程就为: $f[j]=max(f[j],f[j-w[i]k]+v[i]k)$
其中 $j$ 代表01背包当前空间为 $j$ , $i$ 代表这是第 $i$ 个物品 。
但是这样复杂度就太高了 可以使用二进制拆分。将其拆分成大小分别为$1,2,4,8.....m[i]$的物品,如果最后有剩余那么将剩余的作为一个物品,可以发现,拆分出来的这些物品能组成 $1 \rightarrow\ m[i]$ 中的任何数,所以分别对拆分出来的每一个物品跑01背包,一定能求出最优解。

$\color{green}\text{AC代码:}$

#include <bits\/stdc++.h>
using namespace std;
#define reg register
int n,W;\/\/背包大小为W
const int maxn=1e5+5;
const int maxw=4e4+5;
int v[maxn];\/\/价值
int w[maxn];\/\/重量
int m[maxn];\/\/m件
int f[maxn];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
inline void dp()
{
    for(reg int i=1;i<=n;i++)
    {
        int mi=m[i];
        int wi=w[i],vi=v[i];
        int pkg=1;
        while(mi>pkg)
        {
            mi-=pkg;
            wi=w[i]*pkg;
            vi=v[i]*pkg;
            for(reg int j=W;j>=wi;j--)
            {
                f[j]=max(f[j],f[j-wi]+vi);
            }
            pkg*=2;
        }
        wi=w[i]*mi;
        vi=v[i]*mi;
        for(reg int j=W;j>=wi;j--)
        {
            f[j]=max(f[j],f[j-wi]+vi);
        }
    }
}
int main()
{
    n=read();
    W=read();
    for(reg int i=1;i<=n;i++)
    {
        v[i]=read();
        w[i]=read();
        m[i]=read();
    }
    dp();
    printf("%d",f[W]);
    return 0;
}

关于快速傅里叶变换

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-27 22:58:42

前言:

本博客学习于此篇文章

前置:

点值表示法:

$f(x) = {(x0,f[x0]),(x1,f[x1]).....(xn,f[xn])}$
$g(x) = {(x0,g[x0]),(x1,g[x1]).....(xn,g[xn])}$
$f(x) * g(x) = {(x0,f[x0]g[x0]),(x1,f[x1]g[x1]).....(xn,f[xn]*g[xn])} $

快速傅里叶变换中的特殊性质:

设 $F(x)$ 为快速傅里叶变换函数,那么:
$F(f(x)*g(x)) = F(f(x)) * F(g(x))$
也就是通过两个函数的卷积的进行FFT得到的结果 $=$ 分别对两个函数进行FFT后的值相乘 。

复数:

复数由实数和虚数两个部分组成,其中实数部分可以看作横轴上的数,虚数部分可以看作纵轴上的数,同时也用另一个单位 $i$ 作为虚数的单位。 $i$ 满足 $i = \sqrt{-1}$,也就是 $i^{2} = -1$。
设一个复数的实数部分为a,虚数部分为b,那么它的模记为 $| x |$。模:指的就是一个复数到达原点的距离(相当于复数的绝对值),此时可以发现直接使用距离公式计算即可,所以$| x | = \sqrt{a^{2}+b^{2}}$。
复数的乘法(假设为 $(a+bi) * (c+di)$ ) ,那么计算过程:
$(a+bi) * (c+di)$

$ = a(c+di) + bi(c+di)$

$ = ac+adi+bic+bidi$

$ = ac+adi+bi*c+bdi^{2}$

$ = ac+adi+bi*c-bd$

$ = (ac-bd) + (ad+bc)i$

共轭复数:

对于一个复数$c = a+bi$,其共轭复数表示为 $\bar c$。$\bar c = a - bi$,即,将复数部分取反。
$c * \bar c$
$=(a+bi) * (a - bi)$

$=a(a-bi) + bi(a-bi)$

$=a^{2}-abi + abi - b^{2}i^{2}$

$=a^{2}-abi + abi+b^{2}$

$=a^{2}+b^{2}$
可以发现,这就是 $c$的模长的平方。而模长为1时,$a^{2} + b^{2} = |c|^{2} = 1$(说废话呢)

单位根

设一个数 $x$ ,$x^{n} = 1$,那么 $x$就是一个 $n$ 次单位根。
将一个半径为$1$的圆分成n份,n个点分别就是一个 $n$次单位根。
看一下这张图:

从箭头开始的那一点开始,逆时针将 $n$ 个 $n$ 次单位根编号,分别为$\omega^{1}{n},\omega^{2}{n}......\omega^{n}{n}$
$\omega^{k}
{n} = cos(\frac{k}{n} \cdot 2\pi) + sin(\frac{k}{n} \cdot 2\pi)$。
${\omega^{1}{n}}^k = \omega^k_n$
$\omega^{k+\frac{n}{2}}_n = -\omega^{k}
{n}$

推式子:

设多项式 $A(x) = \Sigma^{n-1}{0} a_i = a_0 + a_1x + a_2x^2 + a_3x^3 + ...... + a{n-1}x^{n-1}$
拆开,变成:
$(a_0 + a_2x^2 + a_4x^4 + ... + a_{n-2}x^{n-2}) + (a_1x + a_3x^3 + a_5x^5+ a_{n-1}x^{n-1})$
$= (a_0 + a_2x^2 + a_4x^4 + ... + a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 + a_5x^4+ a_{n-1}x^{n-2})$

两边很相似。

设 $A1(x) = a_0 + a_2x^2 + a_4x^4 + ... + a_{n-2}x^{\frac{n}{2}-1}$
$A2(x) = a_1 + a_3x^2 + a_5x^4+ a_{n-1}x^{\frac{n}{2}-1}$
显然,$A(x) = A1(x^2) + x \cdot A2(x^2)$

设一个值 $k (k < \frac{n}{2})$,代入 $A(x)$:
$A(\omega^k_n) = A1({\omega^k_n}^2) + \omega^k_n \cdot A2({\omega^k_n}^2)$
$= A1({\omega^{2k}_n}) + \omega^k_n \cdot A2({\omega^{2k}_n})$

对于代入 $\omega^{\frac{n}{2} + k}_n$:
$A(\omega^{\frac{n}{2} + k}_n) = A1(\omega^{n+2k}_n) + \omega^{\frac{n}{2} + k}_n \cdot A2(\omega^{n+2k}_n)$
$= A1(\omega^{2k}_n \cdot \omega^n_n) - \omega^k_n \cdot A2(\omega^{2k}_n \cdot \omega^n_n)$

可以发现,$A(\omega^k_n)$ 和 $A(\omega^{\frac{n}{2} + k}_n)$ 结果仅仅是后半部分符号取反。
所以可以通过求前半部分直接得到后半部分。

FFT实现:

递归版 FFT 跑得慢,所以不使用递归版。
每个数的最终位置就是当前位置二进制反转之后的位置,所以可以提前改变位置。
代码(P1919):

#include <bits\/stdc++.h>
#include <complex>
using namespace std;
#define reg register
const double PI=acos(-1);
const int maxn=5e6+5;
typedef long long ll;
complex<double> a[maxn];
complex<double> b[maxn];
ll c[maxn];\/\/存储结果
int rev[maxn];
char given_a[maxn];
char given_b[maxn];
void FFT(complex<double>* num,int n,int inv)
{
    for(reg int i=0;i<n;i++)
    {
        if(i<rev[i])
        {
            swap(num[i],num[rev[i]]);
        }
        \/\/提前更改为fft之后的序列
    }
    for(reg int mid=1;mid<n;mid<<=1)
    {\/\/要处理序列大小的一半
        complex<double> dwg(cos(PI\/mid),inv*sin(PI\/mid));\/\/单位根
        for(reg int i=0;i<n;i+=(mid<<1))
        {\/\/处理到的地方
            complex<double> omega(1,0);
            \/\/逐步求出omega(n,k)
            for(reg int j=0;j<mid;j++,omega*=dwg)
            {
                complex<double> n1=num[i+j];
                complex<double> n2=num[i+j+mid]*omega;
                \/\/根据左边求出右边
                num[i+j]=n1+n2;
                num[i+j+mid]=n1-n2;
            }
        }
    }
}
int main()
{
    scanf("%s",given_a);
    scanf("%s",given_b);
    int len1=strlen(given_a);
    int len2=strlen(given_b);
    for(reg int i=0;i<len1;i++)
    {
        a[i]=given_a[len1-i-1]^48;
    }
    for(reg int i=0;i<len2;i++)
    {
        b[i]=given_b[len2-i-1]^48;
    }
    \/\/-------------------------
    int newlen=1;\/\/计算结果位数
    int bit_cnt=0;\/\/len1+len2二进制
    while(newlen<=len1+len2)
    {
        newlen<<=1;
        \/\/可以看作将其设为2的整数次幂
        \/\/因为fft只能处理2的整数次幂
        bit_cnt++;
    }
    for(reg int i=0;i<newlen;i++)
    {
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit_cnt-1));
    }
    \/\/---------------------------------
    FFT(a,newlen,1);
    FFT(b,newlen,1);
    for(reg int i=0;i<=newlen;i++)
    {
        a[i]*=b[i];
    }
    FFT(a,newlen,-1);
    for(reg int i=0;i<len1+len2;i++)
    {
        c[i]=a[i].real()\/newlen+0.5;
    }
    for(reg int i=0;i<len1+len2;i++)
    {
        c[i+1]+=c[i]\/10;
        c[i]%=10;
    }
    newlen=len1+len2+1;\/\/fft完成后,设置回原来的
    while(c[newlen])\/\/处理剩余的仅为
    {
        c[newlen+1]+=c[newlen]\/10;
        c[newlen]%=10;
        newlen++;
    }
     while(!c[newlen])\/\/处理剩余的仅为
    {
        newlen--;
    }
    for(reg int i=newlen;i>=0;i--)
    {
        printf("%d",c[i]);
    }
    #ifndef ONLINE_JUDGE
    system("pause");
    #endif
    return 0;
}

关于lhx大佬对dijkstra的一个优化

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-28 15:03:58

问题描述:

可以看一下这份60分代码,它TLE了,而且使用了堆优化,常数大小也能接受。

解决方案描述

可以发现,在一个图中,初始时全都是无穷大,不是最优解,所以肯定先有一轮更新。在之后的更新中,如果发现当前目标点已经是最优值,那么不用将目标点加入队列继续更新,因为如果是最优值那么肯定更新过,并且之前更新时肯定从目标点出发过。

根据图来


根据dij的步骤:
1、更新 $1 \rightarrow 2 \rightarrow 5$这条路径。此时 $dis[2]=1$,$dis[5]=100$。
2、更新 $1 \rightarrow 2 \rightarrow 4 \rightarrow 5$这条路径。此时 $dis[2]=1$,$dis[4]=2$,$dis[5]=3$。
3、更新 $1 \rightarrow 3 \rightarrow 4$ ,此时发现4已经是最优解了,略过(可以发现这是因为之前已经更新的时候经过了 4,所以是最优解,同时可以发现之前更新的过程中从4 出发过,所以从 4 出发的路径也都得到了更新,所以不用重新将4加入队列了)
后面的步骤省略

代码实现:

将:

if(dis[to]>dis[u]+edge[i].w){
	dis[to] = dis[u] + edge[i].w;
}
que.push(make_pair(-dis[to], to));
            

改为:

if(dis[to]>dis[u]+edge[i].w){
	dis[to] = dis[u] + edge[i].w;
   que.push(make_pair(-dis[to], to));
}

优化正确性解释:

见第三步解释

效果:

修改后的代码AC了

关于并查集的一个优化

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-04 12:01:42

作用:

能让并查集原地起飞

具体描述:

P5836这道题用并查集来做,TLE了,只得了 $\color{orange}\text{40分}$,提交记录。代码:

#include <bits\/stdc++.h>\/\/希望有生之年能看到CCF用IOI赛制&CSP2022 rp++
\/\/lca调了半天不行,算了用并查集
using namespace std;
const int maxn=	1e6+5;
int n,m;
char lx[maxn];
int f[maxn];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int find(int x)
{
	return x==f[x]?x:find(f[x]);
}

int main()
{
	for(int i=1;i<maxn;i++)f[i]=i;
	scanf("%d%d",&n,&m);
	scanf("%s",lx+1);
    int x,y;
    for(int i=1;i<n;i++)
    {
    	x=read();
    	y=read();
    	if(lx[x]==lx[y])
    	{
    		int ta=find(x);
			int tb=find(y);
			f[ta]=tb;\/\/将相同颜色弄成连通块
		}
    }
    int ai,bi;
    char ci;
    for(int i=1;i<=m;i++)
    {
    	ai=read();
    	bi=read();
    	scanf("%c",&ci);
    	if(find(ai)==find(bi)&&lx[ai]!=ci)printf("0");\/\/如果在同一个连通块且其中一个不符合要求,其他一定不符合要求
    	else printf("1");
    }
    
    return 0;
}

这份代码之所以TLE,是因为并查集find函数太慢,只需要将find函数修改为一下:

int find(int x)
{
	if(x==f[x])return x;
	return f[x]=find(f[x]);
}

就能让并查集原地起飞,快了30倍+,然后AC,吸氧后甚至拿了最优解排行首页(不吸氧在第二页),提交记录,代码:

#include <bits\/stdc++.h>\/\/希望有生之年能看到CCF用IOI赛制&CSP2022 rp++
\/\/lca调了半天不行,算了用并查集
using namespace std;
const int maxn=	1e6+5;
int n,m;
char lx[maxn];
int f[maxn];
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int find(int x)
{
	if(x==f[x])return x;
	return f[x]=find(f[x]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)f[i]=i;
	scanf("%s",lx+1);
    int x,y;
    for(int i=1;i<n;i++)
    {
    	x=read();
    	y=read();
    	if(lx[x]==lx[y])
    	{
    		int ta=find(x);
			int tb=find(y);
			f[ta]=tb;\/\/将相同颜色弄成连通块
		}
    }
    int ai,bi;
    char ci;
    for(int i=1;i<=m;i++)
    {
    	ai=read();
    	bi=read();
    	scanf("%c",&ci);
    	if(find(ai)==find(bi)&&lx[ai]!=ci)putchar('0');\/\/如果在同一个连通块且其中一个不符合要求,其他一定不符合要求
    	else putchar('1');
    }
    return 0;
}

如何学好信竞-看大佬经验分享读后感

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-09 22:32:51

一、要多刷题

多刷题是最重要的,例如csp-jT3。多刷题能增多知识,加快做出题目速度,还能增加经验以及对各种错误的解决能力,到正式赛场上很有用。

二、要学好数学

很多题目都和数学有关系,或者直接就是数学题。 例如蒲神就曾直接用数学知识AC一道蓝题,只花了5分钟左右。所以数学对于做题是很有帮助的。

三、要利用好时间

周一到周五每天时间就一两个小时,要好好利用,尽量多学算法,多刷题。

不能颓废,要坚持

我现在的水平很菜,但是坚持下去,肯定能变得不那么菜越来越好

P1144 最短路计数题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-10 19:39:04

这是一个很lj的题解

难度:$\color{green}\text{普及+/提高}$

$\color{green}\text{AC算法}$:$dijkstra$

题目分析:

根据题目,让求的是从从顶点$1$到其他$n-1$个点的最短路有多少条。既然是要求最短路,肯定要用到$dijkstra$。然后开一个数组$ans[maxn]$来记录到第$i$个点有多少条最短路。在$dijkstra$中在更新最短距离时顺手更新$ans[i]$即可。

具体做法:

$dijkstra$相信大佬们都会,那么如何更新$ans[i]$呢?首先$ans[to]$肯定是使用$ans[u]$来更新,因为$ans[to]$的最短路数肯定就包含$ans[u]$(这不是废话吗)

$dijkstra$更新最短距离时,如果发现$dis[to] > dis[u] + 1$,也就是说当前点$u \rightarrow to$不是最短距离,之前统计的到$to$的最短数($ans[to]$)肯定都要全部作废,也就是:

ans[to] = ans[u];

如果是当前距离就是最短距离,那么直接加上从$u$转移过来的路径数($ans[u]$)即可,也就是:

ans[to] += ans[u];

到此,本题就做完了,要记得取模。下面是代码:

#include <bits\/stdc++.h>
using namespace std;
#define reg register
const int maxn=1e6+5;
const int maxm=4e6+5;
int n,m;
int dis[maxn];
int head[maxn];
int ans[maxn];
bool vis[maxn];
struct EDGE
{
	int to,nxt;
}edge[maxm];
int tot;
inline void add(int u,int to)
{
	edge[++tot].to=to;
	edge[tot].nxt=head[u];
	head[u]=tot;
}
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
inline void solve()
{
	memset(dis,0x3f3f3f3f,sizeof(dis));
	priority_queue< pair<int,int> > que;
	que.push(make_pair(0,1));
	dis[1]=0;
	ans[1]=1;
	reg int u;
	while(!que.empty())
	{
		u=que.top().second;
		que.pop();
		if(vis[u])continue;
		vis[u]=1;
		reg int to;
		for(reg int i=head[u];i;i=edge[i].nxt)
		{
			to=edge[i].to;
			if(dis[to]>dis[u]+1)
			{
				dis[to]=dis[u]+1;
				ans[to]=ans[u];
				que.push(make_pair(-dis[to],to));
			}
			else if(dis[to]==dis[u]+1)
			{
				ans[to]+=ans[u];
				ans[to]%=100003;
			}
		}
	}
	for(reg int i=1;i<=n;i++)
	{
		printf("%d\n",ans[i]);
	}
}
int main()
{
	n=read();
	m=read();
	reg int x,y;
	for(reg int i=1;i<=m;i++)
	{
		x=read();
		y=read();
		add(x,y);
		add(y,x);
	}
	solve();
	return 0;
}

新幻灯片

共 320 篇博客