9.19 考试总结

前言:

貌似自己的开题和别人好像不太一样,别人都是 \(T1-T2-T3\) ,好像没几个像我一样 \(T3-T2-T1\) 的。大雾

T1 潜伏

题目描述:

⼩悠回家之后,跟着⺟亲看了很多抗⽇神剧,其中不乏⼀些谍战⽚。

*夕,北平城内潜伏着若⼲名地下党员,他们居住在城市的不同位置。现在身为地下党第⼀指挥官
的你,想知道地下党员之间的最⼩通信距离,即从某⼀地下党员住处前往另⼀地下党员住处的距离的最
⼩值。
我们可以将北平城视为⼀张 \(N\) 个点 \(M\) 条边的⽆向图,每条边连接两个点 \(u_i,v_i\),且长度为 \(w_i\)。

输入格式:

每个测试点包含多组数据。

第⼀⾏,给出数据组数 \(T\),之后依次输⼊每组数据。

每组数据的第⼀⾏ \(N\),\(M\) , \(K\) 分别表示点数,边数,地下党员数。

之后 \(M\) ⾏,每⾏ \(u_i,v_i,w_i\) 表示第 \(i\) 条边。

之后⼀⾏,\(k\) 个整数代表地下党员所在结点。

结点编号为 \(1\) 到 \(N\),保证 \(0 \leq k \leq N\)。

输出格式:

对于每组数据,输出⼀⾏⼀个整数,表示地下党员之间的最⼩通信距离。

如果最⼩通信距离为 \(\infty\),请输出 \(-1\) 代替

说明与提示:

对于所有测试点 \(T\leq 100\)。

对于 \(50%\) 的测试点 \(N\leq 1000\),\(M\leq 2000\) 。

对于 \(20%\) 的测试点 \(N\leq 100000\),输⼊的⽆向图⽆环。

对于30%的测试点 \(N\leq 100000\),\(m\leq 200000\) 。

所有 \(0\leq w_i\leq 1000\) 。

一个比较裸的多源最短路问题,但我当时没想出来用二进制来压缩状态,直接就GG了。

只能打个 \(50pts\) 暴力滚粗。

50pts Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define int long long
const int N = 1e5+10;
int T,n,m,k,u,v,w,ans,tot;
int head[N],dis[N],c[N];
bool vis[N];
struct node
{
	int to,net,w;
}e[N<<1];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void add(int x,int y,int w)
{
	e[++tot].to = y;
	e[tot].w = w;
	e[tot].net = head[x];
	head[x] = tot;
}
void spfa(int x)
{
	priority_queue<pair<int,int> > q;
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	q.push(make_pair(0,x)); dis[x] = 0; 
	while(!q.empty())
	{
		int t = q.top().second; q.pop();
		if(vis[t]) continue; 
		vis[t] = 1;
		for(int i = head[t]; i; i = e[i].net)
		{
			int to = e[i].to;
			if(dis[to] > dis[t] + e[i].w)
			{
				dis[to] = dis[t] + e[i].w;
				q.push(make_pair(-dis[to],to));
			} 
		}
	}
}
void qingkong()
{
	tot = 0;
	memset(head,0,sizeof(head));
	memset(dis,0,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(c,0,sizeof(c));
}
signed main()
{
	// freopen("hide.in","r",stdin);
	// freopen("hide.out","w",stdout);
	T = read();
	while(T--)
	{
		qingkong();
		n = read(); m = read(); k = read(); ans = 1e15;
		for(int i = 1; i <= m; i++)
		{
			u = read(); v = read(); w = read();
			add(u,v,w); add(v,u,w);
		}
		for(int i = 1; i <= k; i++) c[i] = read();
		for(int i = 1; i <= k; i++)
		{
			spfa(c[i]);
			for(int j = i+1; j <= k; j++) 
			{
				ans = min(ans,dis[c[j]]);
			}
		}
		if(ans == 1e15) printf("%lld\n",-1);
		else printf("%lld\n",ans);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 神经

题目描述:

神经⽹络可以表示为 \(N\) 个点 \(M\) 条边的有向图,每个结点带有⼀个兴奋权值 \(w_i\)。

如果以 \(x\) 单位⼤⼩的电流刺激结点 ,设 \(v_1,v_2,.....v_k\) 是从 \(x\) 出发可以到达的结点,

则神经⽹络会产⽣ \(x\times max(w_{v_1},w_{v_2}.....w_{v_k})\)的兴奋度,请注意,我们认为从 \(u\) 出发可以到达 \(u\) 。

现在请你回答若⼲询问,每个询问表示为:在以 \(x\) ⼤⼩的电流刺激 \(u\) 点后,⽹络的兴奋度是多少。

输入格式:

每个测试点包含多组数据。

第⼀⾏,⼀个整数 \(T\) 表示数据组数,之后依次输⼊每组数据。

每组数据第⼀⾏, \(N,M,K\) 分别表示点数,边数,询问次数。

之后⼀⾏, \(N\) 个整数, \(w_1,w_2,.....w_N\)表示每个点的兴奋权值。

之后 \(M\) ⾏,每⾏ \(u_iv_i\) 表示⼀条从 \(u_i\) 到 \(v_i\) 的单向边。

之后 \(k\) ⾏,每⾏ \(u,x\) 表示⼀次刺激。

输出格式:

对于每个测试点中的每个询问,输出⼀⾏⼀个整数表示答案。

数据范围与提示:

对于所有数据, \(T\leq 5\)

对于50%的测试点, \(N\leq100,M\leq 2000,K\leq 1000\)。

对于20%的测试点, \(N\leq 100000,M\leq 200000,K\leq 100000\),输⼊的有向图构成DAG。

对于30%的测试点 \(N\leq 200000,M\leq 400000,K\leq 100000\)。

所有 \(0\leq x,w_i\leq 10^9\)。

这道题在数据范围的时候已经给出做法了,输入保证有向图构成 \(DAG\) 直接对整张图跑一边 拓扑排序加 \(dp\) 就可以解决。

但数据没有保证不存在环,所以我们需要对整张图缩一下点,然后对每个强连通分量再跑拓扑排序。

另外一种写法就是对整张图跑 \(dij\) 进行松弛操作,那种写法实现起来要比这个好写点。

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
#define int long long
const int N = 2e5+10;
int T,n,m,k,u,o,x,num,top,cnt,tot;
int dfn[N],low[N],head[N],shu[N],w[N],f[N],du[N],sta[N];
bool vis[N];
vector<int> v[N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
struct node
{
	int to,net;
}e[N<<1];
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void tarjain(int x)//缩点
{
	dfn[x] = low[x] = ++num;
	sta[++top] = x; vis[x] = 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(!dfn[to])
		{
			tarjain(to);
			low[x] = min(low[x],low[to]);
		}
		else if(vis[to])
		{
			low[x] = min(low[x],dfn[to]);
		}
	} 
	if(dfn[x] == low[x])
	{
		cnt++; int y;
		do
		{
			y = sta[top--];
			shu[y] = cnt;
			f[cnt] = max(f[cnt],w[y]);
			vis[y] = 0;
		}while(x != y);
	}
}
void rebuild()
{
	for(int i = 1; i <= n; i++)
	{
		for(int j = head[i]; j; j = e[j].net)
		{
			int to = e[j].to;
			if(shu[i] != shu[to])
			{
				v[shu[to]].push_back(shu[i]);
				du[shu[i]]++;
			}
		}
	}
}
void tupo()//拓扑排序加dp
{
	queue<int> q;
	for(int i = 1; i <= cnt; i++)
	{
		if(du[i] == 0) q.push(i);
	}
	while(!q.empty())
	{
		int t = q.front(); q.pop();
		for(int j = 0; j < (int) v[t].size(); j++)
		{
			int to = v[t][j];
//			cout<<to<<" "<<t<<endl;
			f[to] = max(f[to],f[t]);
			if(--du[to] == 0) 
			{
				q.push(to);
			}
		}
	}
}
void qingkong()
{
	tot = num = cnt = top = 0;
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(f,0,sizeof(f));
	memset(vis,0,sizeof(vis));
	memset(du,0,sizeof(du));
	memset(w,0,sizeof(w));
	memset(shu,0,sizeof(shu));
	for(int i = 1; i <= n; i++) v[i].clear();
}
signed main()
{
	// freopen("neural.in","r",stdin);
	// freopen("neural.out","w",stdout);
	T = read();
	while(T--)
	{
		qingkong();
		n = read(); m = read(); k = read();
		for(int i = 1; i <= n; i++) w[i] = read();
		for(int i = 1; i <= m; i++)
		{
			u = read(); o = read();
			add(u,o);
		}
		for(int i = 1; i <= n; i++) //缩点
		{
			if(!dfn[i]) tarjain(i);
		}	
		rebuild(); tupo();//重新建图加拓扑排序
		for(int i = 1; i <= k; i++)
		{
			u = read(); x = read();
			printf("%lld\n",x * f[shu[u]]);
		}
	}
	fclose(stdin); fclose(stdout);
	return 0;
}

T3计树

题目描述:

小悠的导师对于树叶颜⾊有很深的研究,但是碍于眼神不好,总是要请⼩悠来帮忙数⼀数树上某种颜⾊的叶⼦有多少⽚。

给出一棵 \(N\) 个结点的树,每个结点初始具有⼀个颜⾊ ,现在有如下两种操作——

  1. 更改结点 \(u\) 的颜⾊为 \(x\) 。
  2. 询问从 \(u\) 到 \(v\) 的路径上有多少个颜⾊为 \(x\) 的结点。

现在请你按顺序完成若⼲次操作。

输入格式:

每个测试点包含多组数据。

第 \(1\),个整数 表示数据组数 \(T\),之后依次输⼊每组数据。

每组数据第 \(N\),\(M\) 分别表示点数、操作次数。
之后 \(N\) 个整数 表示每个点的初始颜⾊。
之后 \(n-1\) ,每⾏两个整数 \(u\),\(v\) ,表示树上存在⼀条连接 的边。
之后 \(m\) ,表示每个操作。
如果是操作 \(1\),则形如 \(1,u,x\)。
如果是操作 \(2\),则形如 \(1,u,v,x\)。

输出格式:

对于每组数据的每个操作2,输出⼀⾏⼀个整数表示答案。

数据范围与提示:

对于所有测试点 \(T \leq 5\)。

对于50%的测试点 \(N\leq 1000\),\(M\leq 1000\) 。

对于20%的测试点 \(N\leq 100000\),\(M\leq 100000\) ,颜⾊种类数不超过5。

对于30%的测试点 \(N\leq 100000\),\(M\leq 100000\) 。

所有测试点颜⾊种类数均不超过 \(100\) 。

一开始我想这不就是把数颜色那道题套的树上了吗?直接上树上带修莫队,然鹅自己并不会写。

看了一眼数据范围才发现,这道题是个树剖套线段树的裸题,直接对每个颜色开个线段树来维护就好了。

三十分钟码完,结果debug 用了一个小时。

为了卡一下时间,我还特意把建树过程省了。结果在 \(lemon\) 上测还是 \(TLE\) ,但在 \(oj\) 上就测得没问题,1e5的数据完全可以跑过去。

\(lemon\) 差评,大雾

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5+10;
int n,m,u,v,x,tot,num,T,opt;
int head[N],dep[N],dfn[N],siz[N],son[N],fa[N],top[N];
int tr[110][N<<2],col[N];
struct node
{
	int to,net;
}e[N<<1];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void get_tree(int x)
{
	dep[x] = dep[fa[x]] + 1; siz[x] = 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa[x]) continue;
		fa[to] = x;
		get_tree(to);
		siz[x] += siz[to];
		if(siz[to] > siz[son[x]]) son[x] = to; 
	}
}
void dfs(int x,int topp)
{
	top[x] = topp; dfn[x] = ++num; 
	if(son[x]) dfs(son[x],topp);
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa[x] || to == son[x]) continue;
		dfs(to,to);
	}
}
void up(int now,int o)
{
	tr[now][o] = tr[now][o<<1] + tr[now][o<<1|1];
}
void chenge(int now,int o,int L,int R,int x,int val)
{
	if(L == R)
	{
		tr[now][o] += val;
		return;
	}
	int mid = (L + R)>>1;
	if(x <= mid) chenge(now,o<<1,L,mid,x,val);
	if(x > mid) chenge(now,o<<1|1,mid+1,R,x,val);
	up(now,o);
}
int query(int now,int o,int L,int R,int ql,int qr)
{
	int res = 0;
	if(ql <= L && qr >= R) return tr[now][o];
	int mid = (L + R)>>1;
	if(ql <= mid) res += query(now,o<<1,L,mid,ql,qr);
	if(qr > mid) res += query(now,o<<1|1,mid+1,R,ql,qr);
	return res;
}
int ask(int x,int y,int now)
{
	int res = 0;
	while(top[x] != top[y])
	{
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		res += query(now,1,1,n,dfn[top[x]],dfn[x]);
		x = fa[top[x]];
	}
	if(dep[x] >= dep[y]) swap(x,y);
	res += query(now,1,1,n,dfn[x],dfn[y]);
	return res;
}
void qingkong()
{
	tot = num = 0;
	memset(col,0,sizeof(col));
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(tr,0,sizeof(tr));
	memset(dep,0,sizeof(dep));
	memset(fa,0,sizeof(fa));
	memset(siz,0,sizeof(siz));
	memset(son,0,sizeof(son));
	memset(top,0,sizeof(top));
}
int main()
{
//	freopen("count.in","r",stdin);
//	freopen("count.out","w",stdout);
	T = read();
	while(T--)
	{
		qingkong();
		n = read(); m = read();
		for(int i = 1; i <= n; i++) col[i] = read();
		for(int i = 1; i <= n-1; i++)
		{
			u = read(); v = read();
			add(u,v); add(v,u);
		}
		get_tree(1); dfs(1,1);
		for(int i = 1; i <= n; i++)
		{
			chenge(col[i],1,1,n,dfn[i],1);
		}
		for(int i = 1; i <= m; i++)
		{
			opt = read();
			if(opt == 1)
			{
				u = read(); x = read();
				chenge(col[u],1,1,n,dfn[u],-1);
				chenge(x,1,1,n,dfn[u],1);
				col[u] = x;
			}
			else if(opt == 2)
			{
				u = read(); v = read(); x = read();
				printf("%d\n",ask(u,v,x));
			}
		}
	}
	fclose(stdin); fclose(stdout);
	return 0;
}

口胡一下正解的写法吧,我们可以对于每个颜色都开个 \(vector\) 存涉及到这个颜色的操作,也就是把颜色离线下来,之后再枚举每个颜色,统计每个询问的答案就

可以,复杂度貌似是 \(clog^2n\) 的,跑的飞快 \(300ms\) 都可以跑过去。

总结

期望得分:50+100+100 = 250

实际上因为 \(lemon\) 的一些问题,直接炸成 \(50+100+70 = 220\) ,芜湖原地起飞、

感觉这套题的思维难度不是很大,但代码难度的话却不一样了,写完了 \(debug\) 就需要好几个小时。

这难道就是所谓的 码农局,大雾

另外,每题都是多组数据,每次都要注意清空,好烦。

上一篇:【dp每日一题】HDU 1176 免费馅饼


下一篇:BZOJ 3288 Mato矩阵 解题报告