CF gym-102452 2019-2020 ICPC Asia Hong Kong Regional Contest

B. Binary Tree

给你一棵树,两个人每次只能移走一颗子树,这个子树必须是满二叉树(叶子也行),第一个人先开始,谁不能操作谁就输了,问你谁能赢。

满二叉树是奇数个节点((2^x-1)),那么每次都是移走奇数个点,判奇偶性就行了。

D. Defining Labels

进制转换,把给的数的10进制转化为带限制k进制,每一位都要在原本认知的基础上-1。

以k=10为例:

x = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (十进制)
0 1 2 3 4 5 6 7 8 9 00 01 02 03 04 (十进制每一位还要-1)

如果要满足每一位-1,上面9和10是对应的,如果对每一位-1,那么10第一位-1就要把第二位退一位下来;如果先转10进制再模拟减法退位应该是可行的,代码量也应该不会很大。

还有一种做法是:

void solve(int k, int x)
{
    if (!x)return;
    x--;
    solve(k, x / k);
    ans.push_back((x % k) + 10 - k);
}

假设x=114514,k=5,我们虽然不知道x的5进制表示,但如果x的五进制表示的最后一位为0时,把x--之后它的五进制也自动进行了退位,不为0时就是最后一位-1,这样x%k得到的就是最后一位的位权,那么再把这个x/=k,相当于把x右移一位,这样递归下去直到x==0时就把x分解完了w

最后再加上题目条件10-k即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
#define ll long long
#define pii pair<int,int>
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

ll mod = 1e9 + 7;

vector<int>ans;

void solve(int k, int x)
{
    if (!x)return;
    x--;
    solve(k, x / k);
    ans.push_back((x % k) + 10 - k);
}

int main()
{
    fastio;
    int t;
    cin >> t;
    while (t--)
    {
        ans.clear();
        ll k, x;
        cin >> k >> x;
        solve(k, x);
        for (auto i : ans)
            cout << i;
        cout << endl;
    }
    return 0;
}

E. Erasing Numbers

给长度为n的排列,每次操作选择连续的3个数,然后删除最大的和最小的数,进行很多次最终留下一个数。问你这个排列中哪些数可以在进行某一系列操作之后留下来。

记当前的数为x,比它大的数变为1,比它小的数变为0,比较0和1的个数:

①如果0和1的个数相同,那么肯定可以让这个数最终留下来(给出一种示例操作:由于0和1的个数相同,那么每次操作最理想的就是移除一个0一个1。出现0x1和1x0时移除x两侧的0和1;剩下的情况全部枚举出来:000、111、011、100、。。。操作一个000时肯定要对应的操作一个111,不过大部分时候连续0周围都会有1,这样就可以满足移除一个0一个1,连续1同理)

②如果0和1的个数不同,那么就要把多出来的数删除。由于只存在两种删除模式(删除2个相同的数(3个连续相同的数可以这样)和各删除一个),很显然只能去寻找删除2个相同的数这种情况。

让cnt0和cnt1做差,如果为奇数就凑不出来;

否则就再里面找连续的去删,看看能不能删到0和1的数量相同的状态,再去用各减1的操作处理。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int inf = 1e9 + 7;
const ll lnf = 1e18 + 7;
const int maxn = 1e6 + 10;
ll mod = 998244353;

bool ans[maxn];

int n;

int solve(int x,vector<int>&a)
{
	int cnt = 0, tot = 0;
	for (int i = 1; i <= n; i++)
		if (a[i] > a[x])cnt++;
		else if (a[i] < a[x])tot++;
	if (cnt == tot)return 1;
	if (abs(cnt - tot) & 1)return 0;
	int flag = (cnt > tot ? 1 : -1);
	int sum = 0;
	for (int i = 1; i <= n; i++)
	{
		if (i == x)
		{
			sum = 0;
			continue;
		}
		if (a[i] > a[x])
			sum += flag;
		else sum -= flag;
		if (sum < 0)
			sum = 0;
		if (sum == 3)
		{
			if (flag == 1)
				cnt -= 2;
			else tot -= 2;
			sum = 1;
			if (cnt == tot)
				return 1;
		}
	}
	return 0;
}

int main()
{
	fastio;
	int t;
	cin >> t;
	while (t--)
	{
		cin >> n;
		vector<int>a(n + 1);
		for (int i = 1; i <= n; i++)
			cin >> a[i], ans[i] = 0;
		for (int i = 1; i <= n; i++)
			ans[i] = solve(i, a);
		for (int i = 1; i <= n; i++)
			cout << ans[i] << "";
		cout << endl;
	}
	return 0;

}

G. Game Design

构造一棵树,每个点可以安排一个权值,选择它获得权值并覆盖它可以到达的所有叶子节点。需要构造的这棵树有k种权值和最小的选择方式选择一些点覆盖所有的叶子节点。

在纸上画画可以发现一个规律,对于两棵树,把他们连向一个构造根,操作数相乘,当这个根的权值等于两棵树所有叶子节点的权值和时操作数再+1,否则不加。(说简单一点就是子树合并满足乘法原理,连向的根需要权值特判)

可以想到一个数可以被这种方式分解:(117 = 116 + 1 = (58 * 2) + 1 =(29 * 2 * 2) + 1 = ((28 + 1) * 2 * 2) + 1) .......

每次+1可以构造新根连向上一个构造根,*2可以把一个大小为2的链连向当前构造根。

最后把叶子节点的所有权值赋为1,跑一遍dfs统计一下各个非叶子节点的权值。

如果k一开始就是偶数那么这棵树的root可以赋个无限大权值就不用-1了。

由于这个数只有1e9,大概质因数分解也可以构造,k更大一些出现大质数就不能质因数分解了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
#define ll long long
#define pii pair<int,int>
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)

ll mod = 1e9 + 7;
int c[maxn], fa[maxn];

vector<int>G[maxn];

int dfs(int from)
{
    int res = G[from].empty();
    for (auto to : G[from])
        res+=dfs(to);
    return c[from] = res;
}

int main()
{
    fastio;
    int k;
    cin >> k;
    int K = k;
    if (k == 1)
    {
        cout << 2 << endl;
        cout << 1 << endl;
        cout << 10000 << " " << 1 << endl;
        return 0;
    }
    c[1] = (k & 1 ? 1 : 1e8);
    if (k & 1)
        k--;
    int cnt = 1;
    int last = 1;
    int tot = 1;
    while (k > 1)
    {
        while (k % 2 == 0)
        {
            ++cnt;
            G[last].push_back(cnt);
            fa[cnt] = last;
            ++cnt;
            G[cnt - 1].push_back(cnt);
            fa[cnt] = cnt - 1;
            k /= 2;
        }
        if (k == 1)
            break;
        k--;
        ++cnt;
        G[last].push_back(cnt);
        fa[cnt] = last;
        last = cnt;
    }
    dfs(1);
    if (K % 2 == 0)
        c[1] = 1e8;
    cout << cnt << endl;
    for (int i = 2; i <= cnt; i++)
        cout << fa[i] << " ";
    cout << endl;
    for (int i = 1; i <= cnt; i++)
        cout << c[i] << " ";
    return 0; 

}

J. Junior Mathematician

让你求L到R区间内满足条件的在模m意义下的x同余f(x)的x的个数。

f(x)的定义:

这个式子含义就是:累加每一位的贡献,它的贡献就是它的权值乘上len到上一位的前缀和。

以下为设计状态的过程,WA+TLE了一上午

很显然是数位dp,最开始想dp[pos][limit][][],后两维表示pos到len所有位的和、所有位累加的f(x),然后发现记录前缀和相同的状态它们所对应的前面一段x对m取模可能不同,故又加上一维,然后爆空间了。

然后发现(f(x)equiv x(mod m))可以变成(f(x)-x equiv 0(mod m)),这样后面又只需要两维:对于当前位置pos,第一维是pos到len这一段x对m取模的权值,第二维是pos到len这一段f(x)-x的权值(要+m %m弄成正的)。
第一维我尝试去用

(X * 10 + i) % m

去取前缀模,发现是错的,因为这种取模方式得到的结果要跑完整个长度才是正确的(后面很多个*10都没有累乘到前面去取模)

需要先去打一个表,把每一位需要乘多少个10记录下来:

		for (int i = 1; i < max(s.length(), p.length()); i++)
			biao[i] = biao[i - 1] * 10 % m;

导致TLE35的原因之一,计算多了,形参多了:

最后就是ac代码,这个题目卡常,不该取模的地方不要取模,函数的形参能少开就少开:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
const int inf = 1e9 + 7;
const ll lnf = 1e18 + 7;
const int maxn = 1e6 + 10;
int mod = 1e9 + 7;

int m;

int dp[5005][65][65];

int biao[5050];

int len = 0, b[5050];

int dfs(int pos, int flag, int res, int sum)
{
	if (pos == -1)
		return res == 0;
	if (!flag && ~dp[pos][sum][res])return dp[pos][sum][res];
	int r = flag ? b[pos] : 9;
	ll tmp = 0;
	for (int i = 0; i <= r; i++)
		tmp += dfs(pos - 1, flag && i == b[pos], ((res + sum * i - i * biao[pos]) % m + m) % m, (sum + i) % m);
	tmp %= mod;
	if (!flag)
		return dp[pos][sum][res] = tmp;
	else 
		return tmp;
}

void init(int len)
{
	//memset(dp, -1, sizeof(dp));
	for (int i = 0; i < len; i++)
			for (int k = 0; k < m; k++)
				for (int l = 0; l < m; l++)
					dp[i][k][l] = -1;
}

int main()
{
	fastio;
	int t;
	cin >> t;
	biao[0] = 1;
	while (t--)
	{
		string s, p;
		cin >> s >> p >> m;
		for (int i = 1; i < max(s.length(), p.length()); i++)
			biao[i] = biao[i - 1] * 10 % m;
		int ok = 0;
		for (int i = s.length() - 1; i >= 0; i--)
		{
			if (s[i] == '0')
				s[i] = '9';
			else
			{
				s[i] --;
				break;
			}
		}
		if (s[0] == '0')ok = 1;
		len = 0;
		init(max(s.length(), p.length()));
		for (int i = s.length() - 1; i >= ok; i--)
			b[len++] = s[i] - '0';
		int res1 = dfs(len - 1, 1, 0, 0);
		len = 0;
		for (int i = p.length() - 1; i >= 0; i--)
			b[len++] = p[i] - '0';
		int res2 = dfs(len - 1, 1, 0, 0);
		cout << (res2 - res1 + mod) % mod << "
";
	}
	return 0;

}
原文地址:https://www.cnblogs.com/ruanbaiQAQ/p/14659829.html