牛客小白月赛23【题解】

https://ac.nowcoder.com/acm/contest/4784


A、膜法匹配

状压枚举行,复杂度 O(nm*2^n) 过了,过的有点莫名其妙。还跑得贼快 15ms

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
char s[20][100005];
int lie[100005];
int temp[100005];
int main()
{
    int T;
    sc("%d", &T);
    while (T--)
    {
        int n, m, a, b;
        sc("%d%d%d%d", &n, &m, &a, &b);
        for (int i = 1; i <= m; i++)
            lie[i] = 0;
        for (int i = 1; i <= n; i++)
        {
            sc("%s", s[i] + 1);
            for (int j = 1; j <= m; j++)
                if (s[i][j] == '*')
                    lie[j]++;
        }
        for (int i = 0; i < (1 << n); i++)
        {
            for (int j = 1; j <= m; j++)
                temp[j] = lie[j];
            int num = 0, cnt = 0;
            for (int j = 1; j <= n; j++)
            {
                if (i & (1 << (j - 1)))
                    num++;
            }
            if (num != a)
                continue;
            for (int j = 1; j <= n; j++)
            {
                if (i & (1 << (j - 1)))
                {
                    for (int k = 1; k <= m; k++)
                        if (s[j][k] == '*')
                            temp[k]--;
                }
            }
            for (int j = 1; j <= m; j++)
            {
                if (temp[j])
                    cnt++;
            }
            if (cnt <= b)
            {
                pr("yes\n");
                goto qwe;
            }
        }
        pr("no\n");
        qwe:;
    }
}

B、阶乘

枚举质因子 p,求出那个质因子个数之后。通过二分来check 个数是否足够。

考虑check的时候,(实际上就是某场div2的C),二分质因子 p 的倍数,假设我们现在选择 p*mid,我们不考虑每个数字含有的 p 的个数,我们考虑选择含有 p, p^2, p^3 因子的个数,显然这样很好求,就是  \lfloor \frac{p*mid}{p} \rfloor,\lfloor \frac{p*mid}{p^2} \rfloor,\lfloor \frac{p*mid}{p^3} \rfloor,由于因子最多只有30+个,所以在check 的同时加一个特判就可以了。

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
bool check(ll k, ll num, ll all)
{
	ll ans = 0;
	for (int i = num; i <= k * num; i = i * num)
	{
		ans += k * num / i;
		if (ans > 50)
			return true;
	}
	if (ans >= all)
		return true;
	return false;
}
ll get(ll n)
{
	ll ans = 1;
	for (ll i = 2; i * i <= n; i++)
	{
		if (n % i == 0)
		{
			ll cnt = 0;
			while (n % i == 0)
			{
				n /= i;
				cnt++;
			}
			ll l = 1, r = 1e9;
			while (l + 1 < r)
			{
				ll k = (l + r) / 2;
				if (check(k, i, cnt))
					r = k;
				else
					l = k;
			}
			if (!check(l, i, cnt))
				l = r;
			ans = max(ans, i * l);
		}
	}
	if (n > 1)
	{
		ans = max(ans, n);
	}
	return ans;
}
int main()
{
	int T;
	sc("%d", &T);
	while (T--)
	{
		ll n;
		sc("%lld", &n);
		pr("%lld\n", get(n));
	}
}

C、完全图

完全图就是任意两个点都有边,所以 n-1 条边 多一个连通分量,然后 n-2 条边 多一个连通分量……

但是不太明白为啥出题人出这么大的范围,然后花了很长时间(打开了IDEA)上了一发 BigInteger

import java.math.BigInteger;
import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        BigInteger one = new BigInteger("1");
        Scanner in = new Scanner(System.in);
        int T;
        T=in.nextInt();
        for (int ll=0;ll

E、A+B问题

感觉这种题没啥意义,PHP一时爽

4294967296

F、美丽的序列1

类似今年Camp day1 A(https://ac.nowcoder.com/acm/contest/3979/A),两个题相同在都是从n个范围内选出n个数字,然后算相邻对答案差生的贡献,都用到了期望的可加性

Camp的题是求逆序对的期望,所以只需要考虑相邻的逆序即可

小白赛F是求这个序列最少分成多少个连续不下降子段,所以在考虑相邻变成下降的同时,要乘上除了相邻两个线段的剩下线段的长度积,并且由于无论如何都有一个贡献,在最后加上所有数字任选的情况

然后考虑相邻两段,最多有6种情况,实际上就是一个或者多个等差数列,纸上推一下公式就可以了

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
const ll mod = 1e9 + 7;
struct node
{
	ll l;
	ll r;
	ll len;
	ll inv;
}que[100005];
ll power(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)
			res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
ll inv2 = (mod + 1) / 2;
int main()
{
	int n;
	sc("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		sc("%lld%lld", &que[i].l, &que[i].r);
		que[i].len = que[i].r - que[i].l + 1;
		que[i].inv = power(que[i].len, mod - 2);
	}
	ll ans = 0, ans1 = 1;
	for (int i = 1; i <= n; i++)
		ans1 = ans1 * que[i].len % mod;
	for (int i = 1; i < n; i++)
	{
		ll t = ans1 * que[i].inv % mod * que[i + 1].inv % mod;

		ll l1 = que[i].l, r1 = que[i].r;
		ll l2 = que[i + 1].l, r2 = que[i + 1].r;
		if (l2 < l1)
		{
			if (r2 < l1)
			{
				ans = (ans + que[i].len * que[i + 1].len % mod * t) % mod;
			}
			else if (r2 <= r1)
			{
				ans = (ans + que[i].len * (l1 - l2) % mod * t) % mod;
				ans = (ans + (r1 - l1 + r1 - r2) * (r2 - l1 + 1) % mod * inv2 % mod * t) % mod;
			}
			else
			{
				ans = (ans + (l1 - l2 + r1 - l2) * (r1 - l1 + 1) % mod * inv2 % mod * t) % mod;
			}
		}
		else if (l2 <= r1)
		{
			if (r2 <= r1)
			{
				ans = (ans + (r1 - l2 + r1 - r2) * (r2 - l2 + 1) % mod * inv2 % mod * t) % mod;
			}
			else
			{
				ans = (ans + (r1 - l2) * (r1 - l2 + 1) % mod * inv2 % mod * t) % mod;
			}
		}
		else
		{

		}
	}
	ans = (ans + ans1) % mod;
	pr("%lld\n", ans);
}

G、树上求和

就是求每条边被用了多少次,实际上就是这条边的两端节点个数的乘积,所以在dfs求出sz之后,每条边的使用次数就是 sz*(n-sz),排个序即可。

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
const int MAXN = 1e5 + 5;
struct edge
{
    int to;
    int nex;
}e[MAXN * 2];
int head[MAXN], tot;
void init()
{
    tot = 1;
    memset(head, -1, sizeof(head));
}
void add(int a, int b)
{
    e[tot] = edge{ b,head[a] };
    head[a] = tot++;
}
ll sz[MAXN];
vectorv;
void dfs(int u, int f)
{
    sz[u] = 1;
    for (int i = head[u]; i + 1; i = e[i].nex)
    {
        int v = e[i].to;
        if (v == f)
            continue;
        dfs(v, u);
        sz[u] += sz[v];
    }
}
int main()
{
    init();
    int n;
    sc("%d", &n);
    for (int i = 0; i < n - 1; i++)
    {
        int a, b;
        sc("%d%d", &a, &b);
        add(a, b); add(b, a);
    }
    dfs(1, 0);
    for (int i = 2; i <= n; i++)
        v.push_back(sz[i] * (n - sz[i]));
    sort(v.begin(), v.end(), greater());
    ll ans = 0;
    for (int i = 1; i < n; i++)
    {
        ans += v[i - 1] * i;
    }
    pr("%lld\n", ans);
}

H、奇怪的问题增加了

签到,从后往前遍历一下就可以了

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
bool book[100005];
int main()
{
	int T;
	sc("%d", &T);
	while (T--)
	{
		vectorv[30];
		int n;
		sc("%d", &n);
		for (int i = 0; i < n; i++)
		{
			book[i] = false;
			int t;
			sc("%d", &t);
			v[t].push_back(i);
		}
		ll cnt = 2;
		for (int i = 29; i >= 0; i--)
		{
			if (v[i].size() >= cnt)
			{
				for (int j = 0; j < cnt; j++)
					book[v[i][j]] = true;
				cnt = 0;
				break;
			}
			else
			{
				int len = v[i].size();
				for (int j = 0; j < len; j++)
					book[v[i][j]] = true;
				cnt -= len;
			}
			cnt *= 2;
		}
		if (cnt == 0)
		{
			for (int i = 0; i < n; i++)
				pr("%d", book[i] == true ? 1 : 0);
			pr("\n");
		}
		else
			pr("impossible\n");
	}
}

I、寻找子串

枚举后缀,签到

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
char s[1005];
char ans[1005];
int main()
{
	sc("%s", s);
	int len = strlen(s);
	strcpy(ans, s);
	for (int i = 1; i < len; i++)
	{
		if (strcmp(ans, s+i) < 0)
			strcpy(ans, s+i);
	}
	pr("%s", ans);
}

J、最大的差

签到

#include 
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
ll a[100005];
int main()
{
	int n;
	sc("%d", &n);
	for (int i = 0; i < n; i++)
		sc("%lld", &a[i]);
	sort(a, a + n);
	pr("%lld\n", a[n - 1] - a[0]);
}

 

你可能感兴趣的:(牛客网)