牛客练习赛47 题解

A DongDong破密码:

给定n,m,给出长度为n的01串,每次向后移动一位,移动m-1次,最后求出这n+m-1位每一位的异或值成为密码。
给出密码,问长度为n的01串是怎样的。

2 < = n + m < = 1000000 2<=n+m<=1000000 2<=n+m<=1000000,保证有解

分析:

密码第 i i i位设为 a i a_i ai,原串第 i i i位设为 b i b_i bi
a 1 = b 1 a_1=b_1 a1=b1
a 2 = b 1 a_2=b_1 a2=b1^ b 2 b_2 b2
a 3 = b 1 a_3=b_1 a3=b1^ b 2 b_2 b2^ b 3 b_3 b3
a m = b 1 a_m=b_1 am=b1^ b 2 b_2 b2 b m − 1 b_{m-1} bm1^ b m b_m bm

a n = b n − m + 1 a_n=b_{n-m+1} an=bnm+1^ b n − m + 2 b_{n-m+2} bnm+2 b n − 1 b_{n-1} bn1^ b n b_n bn
上往下推即可推出前 n n n b i b_i bi
时间复杂度 O ( n ) O(n) O(n)

代码:

#include 

#define N 1000005

using namespace std;

int a[N], c[N], n, m;
char s[N];

// 110, 000, 011, 101

int main()
{
	scanf("%d %d", &n, &m);
	scanf("%s", s + 1);
	for (int i = 1; i <= n + m - 1; i++) a[i] = s[i] - '0';
	int now = a[1]; c[1] = a[1];
	printf("%d", a[1]);
	for (int i = 2; i <= n; i++)
	{
		if (i > m) now ^= c[i - m];
		c[i] = a[i] ^ now;
		printf("%d", c[i]);
		now ^= c[i];
	}
	return 0;
}

B DongDong认亲戚:

定义:若A和B是亲戚,B和C是亲戚,那么A和C也是亲戚。
有n个人,m次操作
给出n个人的名字分别是什么(如果出现多个人名字相同,则视为同一个人)(保证姓名是小写字符串)
给出m个操作,每行操作给出一个数opt,两个名字x,y
当opt=1时,表示x,y是亲戚
当opt=2时,表示询问x,y是否是亲戚,若是输出1,不是输出0

1 < = n , m < = 20000 , 名 字 字 符 长 度 小 等 于 10 1<=n,m<=20000,名字字符长度小等于10 1<=n,m<=2000010

分析:

对于每个名字我们可以标记一个不同的 i d id id,然后就是一个裸的并查集,
i d id id可以通过排序去重以后得到,对于 x , y x,y x,y i d id id查询可以利用 l o w e r lower lower_ b o u n d bound bound
时间复杂度: O ( m l o g n ) O(mlogn) O(mlogn)

代码:

#include 

#define N 20005

using namespace std;

int fa[N], n, m;
string s[N];

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

int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) cin >> s[i];
	sort(s + 1, s + n + 1);
	int tot = unique(s + 1, s + n + 1) - s - 1;  
	for (int i = 1; i <= tot; i++) fa[i] = i;
	for (int i = 1; i <= m; i++)
	{
		int opt; string x, y;
		scanf("%d", &opt); cin >> x >> y;
		int pos1 = lower_bound(s + 1, s + tot + 1, x) - s - 1;
        int pos2 = lower_bound(s + 1, s + tot + 1, y) - s - 1;
        if (opt == 1) fa[find(pos1)] = find(pos2);
                 else { if (find(pos1) == find(pos2)) printf("1\n"); else printf("0\n"); }
	}
	return 0;
}

C DongDong跳一跳:

有n根柱子,每根柱子都有一个高度和柱子上面鱼干的数量。
开始的时候可以选择站在任意一根柱子上,每次跳跃不限长度而且只能从左向右跳跃,但只能跳到高度与当前所站高度差绝对值小于等于m的柱子上。
问最多能吃到最多的鱼干(最终不一定要落在第n根柱子上)
1 < = n < = 200000 , 1 < = m < = 500 1<=n<=200000,1<=m<=500 1<=n<=200000,1<=m<=500
对于每根柱子的高度x和鱼干数量y,满足 1 < = x < = 1000000 , 1 < = x , y < = 1000000 1<=x<=1000000,1<=x, y<=1000000 1<=x<=1000000,1<=x,y<=1000000

分析:

d p i dp_i dpi表示最后落到了高度为 i i i的柱子时能获得的最多鱼干
然后从左到右枚举柱子,
对于当前的柱子 k k k,
d p x k = m a x ( d p x k , m a x ( d p x k − m , d p x k − m + 1 , . . . , d p x k + m − 1 , d p x k + m ) + y k dp_{x_k}=max(dp_{x_k},max(dp_{x_k-m},dp_{x_k-m+1},...,dp_{x_k+m-1},dp_{x_k+m})+y_k dpxk=max(dpxk,max(dpxkm,dpxkm+1,...,dpxk+m1,dpxk+m)+yk)
然后 m a x ( d p x k − m , d p x k − m + 1 , . . . , d p x k + m − 1 , d p x k + m ) max(dp_{x_k-m},dp_{x_k-m+1},...,dp_{x_k+m-1},dp_{x_k+m}) max(dpxkm,dpxkm+1,...,dpxk+m1,dpxk+m)可以用线段树维护
时间复杂度就是 O ( n l o g ( x m a x ) ) O(nlog(x_{max})) O(nlog(xmax))
注意一下可能的越界 x k − m < 0 x_k-m<0 xkm<0或者 x k + m > x m a x x_k+m>x_{max} xk+m>xmax

代码:

#include 

#define lson(x) x * 2
#define rson(x) x * 2 + 1
 
#define M 1000005
#define N 200005

using namespace std;

typedef long long ll;

struct Node { int x; ll y; }a[N];
ll dp[M], C[M*5];
int maxnum, n, m;
 
void change(int G, int l, int r, int num1, ll num2)
{
	if (l == r) { C[G] = num2; return; }
	int mid = (l + r) >> 1;
	if (num1 <= mid) change(lson(G), l, mid, num1, num2);
	   else change(rson(G), mid + 1, r, num1, num2); 
	C[G] = max(C[lson(G)], C[rson(G)]);
}

ll Get_max(int G, int l, int r, int ll, int rr)
{
	if (ll == l && rr == r) return C[G];
	int mid = (l + r) >> 1;
	if (rr <= mid) return Get_max(lson(G), l, mid, ll, rr);
	if (ll > mid) return Get_max(rson(G), mid + 1, r, ll, rr);
	if (ll <= mid && rr > mid) return max(Get_max(lson(G), l, mid, ll, mid), Get_max(rson(G), mid + 1, r, mid + 1, rr));
    return 0; 
}

int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d %lld", &a[i].x, &a[i].y), maxnum = max(maxnum, a[i].x);
	dp[a[1].x] = a[1].y; 
    change(1, 1, maxnum, a[1].x, a[1].y);
	for (int i = 2; i <= n; i++)
	{
    	ll now = Get_max(1, 1, maxnum, max(a[i].x - m, 1), min(a[i].x + m, maxnum)) + a[i].y;
    	if (now > dp[a[i].x]) dp[a[i].x] = now, change(1, 1, maxnum, a[i].x, dp[a[i].x]);
    }
	ll ans = 0;
	for (int i = 1; i <= maxnum; i++) ans = max(ans, dp[i]);
	printf("%lld\n", ans);
	return 0;
}

D DongDong坐飞机:

给定n个城市,m条飞机航线,k次半价机会,1为DongDong家,n为萨摩耶家,每条单向边都有起点终点和机票价格(保证所有价格大于0),她可以k次使用半价折扣,求从1到n的最小花费。(若无法从1到n,输出-1)
保证所有价格均为偶数
n < = 10000 , m < = 50000 , k < = 10 , 0 < = w < = 1000000 n<=10000,m<=50000,k<=10,0<=w<=1000000 n<=10000,m<=50000,k<=10,0<=w<=1000000,数据可能有重边和自环

分析:

分层图最短路,
d i s i , j dis_{i,j} disi,j表示到城市 i i i,用了 j j j半价折扣所需要的最小花费
然后直接做 s p f a spfa spfa即可
理论复杂度 O ( n k l o g ( n k ) ) O(nklog(nk)) O(nklog(nk))

代码:

#include 

#define N 10005
#define M 50005

using namespace std;

typedef long long ll;

struct Node { int To, nxt; ll w; }e[M];
struct Code { int u, v; ll w; }a[M];
int ls[N], n, m, K, cnt;
ll dis[N][11], inf = 0x7fffffff;
bool vis[N][11];

queue  Q[2];

bool cmp(Code aa, Code bb)
{
	if (aa.u == bb.u) return aa.v < bb.v;
	return aa.u < bb.u;
}

void Addedge(int u, int v, ll w)
{
	e[++cnt].To = v, e[cnt].w = w, e[cnt].nxt = ls[u], ls[u] = cnt;
}

void spfa()
{
	for (int i = 1; i <= n; i++) 
	    for (int j = 0; j <= K; j++) dis[i][j] = inf * 10;
	Q[0].push(1); Q[1].push(0);
	vis[1][0] = 1; dis[1][0] = 0;
	while (Q[0].size())
	{
		int u = Q[0].front();  Q[0].pop();
		int num = Q[1].front(); Q[1].pop();
		for (int i = ls[u]; i; i = e[i].nxt)
		{
			if (dis[u][num] + e[i].w / 2 <= dis[e[i].To][num + 1] && num + 1 <= K)
			{
				dis[e[i].To][num + 1] = dis[u][num] + e[i].w / 2;
				if (!vis[e[i].To][num + 1]) Q[0].push(e[i].To), Q[1].push(num + 1), vis[e[i].To][num + 1] = 1;
			}
			if (dis[u][num] + e[i].w <= dis[e[i].To][num])
			{
				dis[e[i].To][num] = dis[u][num] + e[i].w;
				if (!vis[e[i].To][num]) Q[0].push(e[i].To), Q[1].push(num), vis[e[i].To][num] = 1;
			}
		}
		vis[u][num] = 0;
	}
} 

int main()
{
	scanf("%d %d %d", &n, &m, &K);
	for (int i = 1; i <= m; i++) scanf("%d %d %lld", &a[i].u, &a[i].v, &a[i].w);
	for (int i = 1; i <= m; i++)
	    if (a[i].u != a[i].v)
	    	if (a[i].u != a[i - 1].u || a[i].v != a[i - 1].v) Addedge(a[i].u, a[i].v, a[i].w);
	spfa();
	ll ans = inf * 10;
	for (int i = 0; i <= K; i++) ans = min(ans, dis[n][i]);
	if (ans == inf * 10) printf("-1\n"); else printf("%lld\n", ans);
	return 0;
}

E DongDong数颜色:

给定一个n个点,n-1条边的树形图(视1号店为根),每个点有一个颜色,m个询问,每次询问以x为根的子树中有多少种不同的颜色。
2 < = n < = 100000 , 1 < = m , c o l o r < = n 2<=n<=100000,1<=m,color<=n 2<=n<=100000,1<=m,color<=n

分析:

a n s i ans_i ansi表示以x为根的子树中有多少种不同的颜色。
然后利用 s e t set set内元素的不重复性,
从下往上合并,
当前 x x x结点的 s e t set set的的大小即为 a n s x ans_x ansx
然后每个询问直接回答即可

代码:

#include 

#define N 100005

using namespace std;

struct Node{ int To, nxt; }e[N*2];
int ans[N], col[N], ls[N], n, m, cnt;

void Addedge(int u, int v)
{
	e[++cnt].To = v, e[cnt].nxt = ls[u], ls[u] = cnt;
	e[++cnt].To = u, e[cnt].nxt = ls[v], ls[v] = cnt;
}

set  dfs(int x, int y)
{
	set  a;
	for (int i = ls[x]; i; i = e[i].nxt)
	{
		if (e[i].To == y) continue;
		set  b = dfs(e[i].To, x);
		for (set::iterator j = b.begin(); j != b.end(); j++) a.insert(*j);
	}
	a.insert(col[x]);
	ans[x] = a.size();
	return a;
}

int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &col[i]);
	int u, v;
	for (int i = 1; i < n; i++) scanf("%d %d", &u, &v), Addedge(u, v);
	ans[1] = dfs(1, -1).size();
	for (int i = 1; i <= m; i++)
	{
		int ask; scanf("%d", &ask); printf("%d\n", ans[ask]);
	}
	return 0;
}

F DongDong的生成树:

待完成

你可能感兴趣的:(暴力/枚举/模拟,C++,并查集,spfa,线段树,动态规划)