2020-2021 ACM-ICPC Brazil Subregional Programming Contest

A. Sticker Album

  • 很显然的一道概率dp题,状态转移方程如下:
  • d p [ i ] = ( d p [ i + A ] + d p [ i + A + 1 ] + . . . + d p [ i + B ] ) / ( B − A + 1 ) + 1 dp[i]=(dp[i+A]+dp[i+A+1]+...+dp[i+B])/(B-A+1)+1 dp[i]=(dp[i+A]+dp[i+A+1]+...+dp[i+B])/(BA+1)+1
  • 要留意一下当 A = 0 A=0 A=0 时候的特判: d p [ i ] = ( d p [ i + A + 1 ] + . . . + d p [ i + B ] + B − A + 1 ) / ( B − A ) dp[i]= (dp[i+A+1]+...+dp[i+B]+B-A+1)/(B-A) dp[i]=(dp[i+A+1]+...+dp[i+B]+BA+1)/(BA)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
double dp[N] = {
    
     0 };
int n, A, B;
int main() {
    
    
    scanf("%d %d %d", &n, &A, &B);
    if (A == 0) {
    
    
        double sum = 0;
        for (int i = n - 1; i >= 0; --i) {
    
    
            sum -= dp[i + B + 1];
            sum += dp[i + 1];
            dp[i] = (sum + B - A + 1) / (B - A);
        }
        printf("%.10lf\n", dp[0]);
    }
    else {
    
    
        double sum = 0;
        for (int i = n - 1; i >= 0; --i) {
    
    
            sum -= dp[i + B + 1];
            sum += dp[i + A];
            dp[i] = sum / (B - A + 1) + 1.0;
        }
        printf("%.10lf\n", dp[0]);
    }
    return 0;
}

B. Battleship

  • 签到题。根据题意将10 * 10的矩阵依次用元素覆盖,若覆盖出现重叠则输出 N N N,否则输出 Y Y Y
  • 直接无脑暴力即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
const int NN = 1e2 + 10;
const int INF = 0x3f3f3f3f;
int n;
bool mp[NN][NN];
bool check(int x, int y) {
    
    
    if (x < 1 || x > 10 || y < 1 || y > 10)
        return false;
    return true;
}
int main() {
    
    
    scanf("%d", &n);
    bool flag = true;
    for (int i = 1; i <= n; ++i) {
    
    
        int x, y;
        int l, op;
        scanf("%d%d%d%d", &op, &l, &x, &y);
        if (op == 0) 
            for (int j = y; j <= y + l - 1; ++j) {
    
    
                if (!check(x, j) || mp[x][j])
                    flag = false;
                mp[x][j] = true;
            }
                
        else 
            for (int j = x; j <= x + l - 1; ++j) {
    
    
                if (!check(j, y) || mp[j][y])
                    flag = false;
                mp[j][y] = true;
            }
    }
    if (flag)
        puts("Y");
    else
        puts("N");
    return 0;
}

C. Concatenating Teams

  • 根据题目意思,对于在 A A A 中的字符串 a i , a j a_i,a_j ai,aj 以及一个字符串 s s s ,满足 a i = a j + s a_i=a_j+s ai=aj+s
  • 同样在 B B B 中的字符串 b i , b j b_i,b_j bi,bj 以及一个字符串 c c c ,满足 b i = b j + c b_i=b_j+c bi=bj+c
  • 那么当 s = c s=c s=c 的时候,就会多次构成相同的字符串
  • 利用这一个特性,找到所有 A A A 中的 s s s 以及 B B B 中的 c c c ,对于 s = c s=c s=c 的所有串而言,把这个串排除在答案外就可以了。
  • 可以暴力的用 h a s h hash hash 并进行空间换取时间,也可以使用前缀树与后缀树做
  • 如下代码是暴力 h a s h hash hash ,time:1855ms
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ull, ull> pii;
typedef pair<ll, ll>pll;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
const int N = 1e5 + 10;
const int M = 1e7 + 10;
const int mod = 1e9 + 7;
const ull seed = 31;
const ll NUM = 5782344;
ll gcd(ll x, ll y) {
    
     if (y == 0) return x; return gcd(y, x % y); }
int n, m;
inline bool cmp(const string& a, const string& b) {
    
    
	return a.size() < b.size();
}
unordered_map<ull, bool>hash_A, hash_B;
unordered_map<ull, bool>pref, suff;
ull prefHash(string s, int st, int ed) {
    
    
	ull ans = 0;
	for (int i = st; i < ed; ++i) {
    
    
		ans = ans * seed + s[i] - 'a' + 1;
	}
	return ans;
}
ull suffHash(string s, int st, int ed) {
    
    
	ull ans = 0;
	for (int i = ed - 1; i >= st; --i) {
    
    
		ans = ans * seed + s[i] - 'a' + 1;
	}
	return ans;
}
bool a[N] = {
    
     false }, b[N] = {
    
     false };
ull Ahash[N], Bhash[N];
unordered_map<ull, bool>vis;
vector<pii>vec[N];
int main() {
    
    
#ifdef _DEBUG
	FILE* _INPUT = freopen("input.txt", "r", stdin);
	//	FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> m >> n;
	vector<string>A(m), B(n);
	for (auto& v : A) cin >> v;
	for (auto& v : B) cin >> v;
	sort(A.begin(), A.end(), cmp);
	sort(B.begin(), B.end(), cmp);
	for (int i = 0; i < m; ++i) {
    
    
		if (!i) {
    
    
			ull tmp = prefHash(A[i], 0, A[i].size());
			hash_A[tmp] = 1;
			Ahash[i] = tmp;
			continue;
		}
		ull ans = 0;
		for (int j = 0; j < A[i].size(); ++j) {
    
    
			ans = ans * seed + A[i][j] - 'a' + 1;
			if (hash_A[ans]) {
    
    
				ull tmp = prefHash(A[i], j + 1, A[i].size());
				suff[tmp] = 1;
				vec[i].push_back({
    
     tmp,ans });
			}
		}
		hash_A[ans] = 1;
		Ahash[i] = ans;
	}
	for (int i = 0; i < n; ++i) {
    
    
		if (!i) {
    
    
			ull tmp = suffHash(B[i], 0, B[i].size());
			hash_B[tmp] = 1;
			Bhash[i] = tmp;
			continue;
		}
		ull ans = 0;
		for (int j = B[i].size() - 1; j >= 0; --j) {
    
    
			ans = ans * seed + B[i][j] - 'a' + 1;
			if (hash_B[ans]) {
    
    
				ull tmp = prefHash(B[i], 0, j);
				pref[tmp] = 1;
				if (suff[tmp]) 
					b[i] = 1, vis[ans] = 1;
			}
		}
		hash_B[ans] = 1;
		Bhash[i] = ans;
	}
	for (int i = 0; i < n; ++i) {
    
    
		if (vis[Bhash[i]]) b[i] = 1;
	}
	vis.clear();
	for (int i = 1; i < m; ++i) {
    
    
		for (auto v : vec[i]) {
    
    
			if (pref[v.first]) a[i] = 1, vis[v.second] = 1;
		}
	}
	for (int i = 0; i < m; ++i) {
    
    
		if (a[i]) continue;
		if (vis[Ahash[i]]) a[i] = 1;
	}
	int ans1 = 0, ans2 = 0;
	for (int i = 0; i < m; ++i) ans1 += !a[i];
	for (int i = 0; i < n; ++i) ans2 += !b[i];
	cout << ans1 << " " << ans2 << '\n';
	return 0;
}

E. Party Company

  • 题意:给定一棵树,其中满足父节点的权值 ≥ 子结点的权值,然后给定查询,每次给定一个起点以及权值左区间和右区间,跑图可以跑到权值在这个区间范围以内的点,问最终每个点都各自跑了多少遍
  • 直接每次查询暴力跑图肯定是不合理的。先对于每一次查询,找到最远的父节点 u u u 并且满足 w u ≤ R w_u\le R wuR ,然后每次向这个点记录 L L L
    • 因为对于每次查找,权值最大的点即往上的结点只有一个,而满足 w x ≥ L w_x\ge L wxL 的点未知。
  • 待所有的 q q q 查询完后,可以利用树上差分的思想,当遍历到一个点 x x x ,在这个点上存了几个 L L L ,那就将这几个 L L L 进行差分操作。操作完后查询当前结点的值即可。
  • 实现上,对于查询过程中向上找父节点,可以考虑树上倍增的方法节约时间;在查询完后的差分操作,可以考虑使用树状数组,对于当前点 x x x,将在这个点的所有的 L L L 放入树状数组中。查询的时候便查找当前点 a x a_x ax ≤ a x \le a_x ax L L L 有多少个。
  • 每次向下搜索后回溯到当前点,需要清楚掉当前结点记录的所有的 L L L ,详情请看代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int M=1e7+10;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
int head[N],cntE=0;
struct edge{
    
    
	int next,to,w;	
}e[M];
void add(int u,int v,int w=0){
    
    
	e[cntE].next=head[u];
	e[cntE].to=v;
	e[cntE].w=w;
	head[u]=cntE++;
}
int n,m;
int fa[N][25] = {
    
     0 }, h[N], lg[N];
void dfs(int x, int fx, int dep) {
    
    
	fa[x][0] = fx;
	h[x] = dep;
	for (int i = head[x]; ~i; i = e[i].next) {
    
    
		int v = e[i].to;
		dfs(v, x, dep + 1);
	}
}
void init() {
    
    
	dfs(1, 0, 1);
	for (int j = 1; j <= 20; ++j) {
    
    
		for (int i = 1; i <= n; ++i) {
    
    
			fa[i][j] = fa[fa[i][j - 1]][j - 1];
		}
	}
	lg[0] = 0;
	for (int i = 1; i <= N - 10; ++i) {
    
    
		lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
	}
}
vector<int>vec[N];
vector<int>num;
int ans[N],a[N];
int get(int x,int r){
    
    
	for(int i=20;i>=0;--i){
    
    
		if(fa[x][i]>=1&&a[fa[x][i]]<=r) x=fa[x][i];
	}
	return x;
}
int sum[N<<1]={
    
    0};
inline int lowbit(int x){
    
    
	return x&(-x);
}
void ad(int x,int w){
    
    
	for(int i=x;i<=N-5;i+=lowbit(i)){
    
    
		sum[i]+=w;
	}
}
int ask(int x){
    
    
	int ans=0;
	while(x){
    
    
		ans+=sum[x];
		x-=lowbit(x);
	}
	return ans;
}
void dfs(int x){
    
    
	for(auto v:vec[x]){
    
    
		ad(v,1);
	}
	ans[x]=ask(a[x]);
	for(int i=head[x];~i;i=e[i].next){
    
    
		int v=e[i].to;
		dfs(v);
	}
	for(auto v:vec[x]){
    
    
		ad(v,-1);
	}
}
int main(){
    
    
	scanf("%d %d",&n,&m);
	memset(head,-1,sizeof(int)*(n+10));cntE=0;
	for(int i=1;i<=n;++i){
    
    
		int x;
		rd(a[i]);rd(x);
		if(i==x) continue;
		add(x,i);
	}
	init();
	while(m--){
    
    
		int x,l,r;scanf("%d %d %d",&x,&l,&r);
		int id=get(x,r);
		vec[id].push_back(l);
	}
	dfs(1);
	for(int i=1;i<=n;++i) printf("%d%s",ans[i],i==n?"\n":" ");
	return 0;
}

F. Fastminton

  • 一道简单模拟题
  • 一场三局两胜的比赛
  • 每一局比赛只要有一个人达到 5 5 5分并且超过对手两分或者直接达到 10 10 10分,则该人胜利
  • 每一次胜利者下一回合就是发球手,每一局比赛开始时左边的人先发球
  • 若还没有人胜利,则每一次 Q Q Q就是询问当前局每个人胜利的场次和当前局所获得的分数, ∗ * 号表示该人下一回合发球
  • 若已经有人胜利,则每一次Q就输出当前两人的胜利场次和胜者
  • 具体输出格式参照题目
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
char s[N];
int main() {
    
    
    scanf(" %s", s + 1);
    int len = strlen(s + 1);
    bool flag = false;
    int ls = 0, rs = 0, RS = 0, LS = 0;
    int win = 0;
    for (int i = 1; i <= len; ++i) {
    
    
        if (!flag) {
    
    
            if (s[i] == 'S') {
    
    
                ++ls;
            }
            else if (s[i] == 'R') {
    
    
                ++rs;
                flag = true;
            }
            bool win = false;
            if (ls >= 5 && ls - rs >= 2 || ls >= 10) {
    
    
                ++LS;
                ls = rs = 0;
            }
            else if (rs >= 5 && rs - ls >= 2 || rs >= 10) {
    
    
                ++RS;
                ls = rs = 0;
            }
            if (win == 0) {
    
    
                if (LS >= 2)
                    win = 1;
                else if (RS >= 2)
                    win = 2;
            }
            if (s[i] == 'Q') {
    
    
                if (win == 1)
                    printf("%d (winner) - %d\n", LS, RS);
                else if (win == 2)
                    printf("%d - %d (winner)\n", LS, RS);
                else {
    
    
                    if (!flag) 
                        printf("%d (%d*) - %d (%d)\n", LS, ls, RS, rs);
                    else
                        printf("%d (%d) - %d (%d*)\n", LS, ls, RS, rs);
                }
            }
        }
        else {
    
    
            if (s[i] == 'S') {
    
    
                ++rs;
            }
            else if (s[i] == 'R') {
    
    
                ++ls;
                flag = false;
            }
            if (ls >= 5 && ls - rs >= 2 || ls >= 10) {
    
    
                ++LS;
                ls = rs = 0;
            }
            else if (rs >= 5 && rs - ls >= 2 || rs >= 10) {
    
    
                ++RS;
                ls = rs = 0;
            }
            if (win == 0) {
    
    
                if (LS >= 2)
                    win = 1;
                else if (RS >= 2)
                    win = 2;
            }
            if (s[i] == 'Q') {
    
    
                if (win == 1)
                    printf("%d (winner) - %d\n", LS, RS);
                else if (win == 2)
                    printf("%d - %d (winner)\n", LS, RS);
                else {
    
    
                    if (!flag)
                        printf("%d (%d*) - %d (%d)\n", LS, ls, RS, rs);
                    else
                        printf("%d (%d) - %d (%d*)\n", LS, ls, RS, rs);
                }
            }
        }
    }
    return 0;
}

G. Game Show!

  • 签到题
  • 找到最大前缀和即可
  • 注意根据题意其可以一次操作都不进行,所以最低分一定是 100 100 100
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n;
int a[N], sum[N];
int main() {
    
    
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    int maxx = -INF;
    for (int i = 1; i <= n; ++i) {
    
    
        sum[i] = sum[i - 1] + a[i];
        maxx = max(maxx, sum[i]);
    }
    printf("%d\n", max(maxx + 100, 100));
    return 0;
}

H. SBC’s Hangar

  • 首先利用好题目给的一点:排完序后 2 ∗ W i ≤ W i + 1 2*W_i\le W_{i+1} 2WiWi+1, 推得 W 1 + W 2 + . . . + W i < W i + 1 W_1+W_2+...+W_i<W_{i+1} W1+W2+...+Wi<Wi+1
  • 利用好这个性质就好办了,对于区间 [ A , B ] [A,B] [A,B] 由于区间比较大,考虑使用 f ( x ) f(x) f(x) 表示当总重量 ≤ x \le x x 的方案数,则最终所求便是 f ( B ) − f ( A − 1 ) f(B)-f(A-1) f(B)f(A1)
  • 在求 f ( x , i , k ) f(x,i,k) f(x,i,k) 的过程中,假设当前遍历到下标 i i i ,对于当前的总重量 x x x ,有两种可能:
    • 不需要 i i i ,从 [ i + 1 , n ] [i+1,n] [i+1,n] 当中挑 k k k 个,此时对答案的贡献是 C n − i k C_{n-i}^{k} Cnik
    • 需要 i i i, 另外再从 [ i + 1 , n ] [i+1,n] [i+1,n] 当中挑 k − 1 k-1 k1 个,此时对答案的贡献是 f ( x − W i , i + 1 , k − 1 ) f(x-W_i,i+1,k-1) f(xWi,i+1,k1)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
const int N = 3e5 + 10;
const int M = 1e7 + 10;
const int mod = 1e9 + 7, inf = 0x3f3f3f3f;
ll gcd(ll x, ll y) {
    
     if (y == 0) return x; return gcd(y, x % y); }
int head[N], cntE = 0;
struct edge {
    
    
	int next, to, w;
}e[M];
void add(int u, int v, int w = 0) {
    
    
	e[cntE].to = v;
	e[cntE].next = head[u];
	e[cntE].w = w;
	head[u] = cntE++;
}
int n, m;
ll c[55][55], l, r;
vector<ll>a;
ll solve(ll x) {
    
    
	ll ans = 0;
	int cnt = 0;
	for (int i = 0; i < n; ++i) {
    
    
		if (a[i] > x) continue;
		ans += c[n - i - 1][m - cnt];
		x -= a[i];
		++cnt;
		if (m == cnt) {
    
    
			++ans;
			break;
		}
	}
	return ans;
}
int main() {
    
    
#ifdef _DEBUG
	FILE* _INPUT = freopen("input.txt", "r", stdin);
	//	FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
	for (int i = 1; i <= 50; ++i) {
    
    
		c[i][0] = c[i][i] = 1;
		for (int j = 1; j < i; ++j) {
    
    
			c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
		}
	}
	rd(n), rd(m);
	a.resize(n);
	for (auto& v : a) rd(v);
	sort(a.rbegin(), a.rend());
	rd(l), rd(r);
	printf("%lld\n", solve(r) - solve(l - 1));
	return 0;
}

I. Interactivity

  • 经典的树形dp题,首先题目要求在查询量最小的情况下完成操作。可知最小的查询量是叶子结点的数量,可以证明当所有的叶子结点的数量都知道后,整颗树的权值都出来了。
  • 在需要数量是所有叶子结点的情况下,现考虑两个状态, d p [ N ] [ 2 ] dp[N][2] dp[N][2]
    • 若当前点需要查询,则记录为 d p [ i ] [ 1 ] dp[i][1] dp[i][1] ;否则若不需要查询,则记录为 d p [ i ] [ 0 ] dp[i][0] dp[i][0]
    • 当不需要查询的时候, d p [ i ] [ 0 ] = ∏ d p [ v ] [ 0 ] dp[i][0]=\prod dp[v][0] dp[i][0]=dp[v][0] ,其中 v v v i i i 的子结点
    • 当需要查询的时候, d p [ i ] [ 1 ] = ∏ d p [ v k ] [ 0 ] ∗ d p [ v j ] [ 1 ] dp[i][1]=\prod dp[v_k][0]*dp[v_j][1] dp[i][1]=dp[vk][0]dp[vj][1] ,其中 j ≠ k j\ne k j=k
    • 最后需要 d p [ i ] [ 0 ] + = d p [ i ] [ 1 ] dp[i][0]+=dp[i][1] dp[i][0]+=dp[i][1] 表示在当前点进行查询; d p [ i ] [ 1 ] dp[i][1] dp[i][1] 表示在当前点未进行查询并且仍欠一个查询。
  • 因为直接对乘数积乘逆元会出错,下面代码采用左右两段来进行乘法运算
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
const int N = 3e5 + 10;
const int M = 1e7 + 10;
const int mod = 1e9 + 7, inf = 0x3f3f3f3f;
ll gcd(ll x, ll y) {
    
     if (y == 0) return x; return gcd(y, x % y); }
int head[N], cntE = 0;
struct edge {
    
    
	int next, to, w;
}e[M];
void add(int u, int v, int w = 0) {
    
    
	e[cntE].to = v;
	e[cntE].next = head[u];
	e[cntE].w = w;
	head[u] = cntE++;
}
ll fp(ll x, ll y) {
    
    
	ll ans = 1;
	while (y) {
    
    
		if (y & 1) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}
int n, m;
ll dp[N][2];
ll pre[N], last[N];
void dfs(int x) {
    
    
	if ((~head[x]) == 0) {
    
    
		dp[x][0] = dp[x][1] = 1;
		return;
	}
	dp[x][0] = 1;
	vector<ll>vec;
	for (int i = head[x]; ~i; i = e[i].next) {
    
    
		int v = e[i].to;
		dfs(v);
		dp[x][0] *= dp[v][0];
		dp[x][0] %= mod;
		vec.push_back(dp[v][0]);
	}
	int sz = vec.size();
	pre[0] = vec[0];last[sz - 1] = vec[sz - 1];
	last[sz] = 1;
	for (int i = 1; i < sz; ++i) pre[i] = pre[i - 1] * vec[i] % mod;
	for (int i = sz - 2; i >= 0; --i) last[i] = last[i + 1] * vec[i] % mod;
	dp[x][1] = 0;
	int pos = 0;
	for (int i = head[x]; ~i; i = e[i].next,++pos) {
    
    
		int v = e[i].to;
		if (pos == 0) dp[x][1] += last[pos + 1] * dp[v][1] % mod;
		else if (pos == sz - 1) dp[x][1] += pre[pos - 1] * dp[v][1] % mod;
		else dp[x][1] += pre[pos - 1] * last[pos + 1] % mod * dp[v][1] % mod;
		dp[x][1] = dp[x][1] < mod ? dp[x][1] : dp[x][1] - mod; 
	}
	dp[x][0] += dp[x][1];
	dp[x][0] = dp[x][0] < mod ? dp[x][0] : dp[x][0] - mod;
}
int main() {
    
    
#ifdef _DEBUG
	FILE* _INPUT = freopen("input.txt", "r", stdin);
	//	FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
	rd(n);
	memset(head, -1, sizeof(int) * (n + 10)); cntE = 0;
	for (int i = 2; i <= n; ++i) {
    
    
		int x; rd(x);
		add(x, i);
	}
	dfs(1);
	printf("%lld\n", dp[1][0]);
	return 0;
}

K. Between Us

  • 根据题目意思,每个人只有两个阵营可选, 设 a i = 0 a_i=0 ai=0 表示 i i i 选择第一个阵营, a i = 1 a_i=1 ai=1 表示第二个阵营
  • 现考虑 i i i 有奇数个朋友,朋友数量为 k k k,因为奇=奇+偶
    • 假设 a i = 0 a_i=0 ai=0 则必须有奇数个 a j = 0 a_j=0 aj=0 ,剩下偶数个 a j = 1 a_j=1 aj=1
    • 假设 a i = 1 a_i=1 ai=1 则必须有奇数个 a j = 1 a_j=1 aj=1 ,剩下偶数个 a j = 0 a_j=0 aj=0
    • 由上可推出一个规律 a j 1 ⊕ a j 2 . . . ⊕ a j k = a i a_{j_1}\oplus a_{j_2}...\oplus a_{j_k}=a_i aj1aj2...ajk=ai
  • 现考虑朋友数量为偶数,因为奇+奇=偶:
    • 无论 a i a_i ai 为何值,必定是奇数个 a j = 1 a_j=1 aj=1 ,奇数个 a j = 0 a_j=0 aj=0
    • 所以满足 a j 1 ⊕ a j 2 . . . ⊕ a j k = 1 a_{j_1}\oplus a_{j_2}...\oplus a_{j_k}=1 aj1aj2...ajk=1
  • 当所有的方程式出来后,直接看方程是否有解即可,最终是一个 n ∗ ( n + 1 ) n*(n+1) n(n+1) 增广矩阵,先进行高斯消元,而后判断:若当前行 a i , 1 . . . a i , n a_{i,1}...a_{i,n} ai,1...ai,n 都为0并且 a i , n + 1 a_{i,n+1} ai,n+1 不为0,此时无解。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
const int N = 1e2 + 10;
const int M = 1e7 + 10;
const int mod = 1e9 + 7, inf = 0x3f3f3f3f;
ll gcd(ll x, ll y) {
    
     if (y == 0) return x; return gcd(y, x % y); }
int head[N], cntE = 0;
struct edge {
    
    
	int next, to, w;
}e[M];
void add(int u, int v, int w = 0) {
    
    
	e[cntE].to = v;
	e[cntE].next = head[u];
	e[cntE].w = w;
	head[u] = cntE++;
}
int n, m;
int d[N] = {
    
     0 };
bitset<N << 1>a[N];
int Gauss_rev(int n) {
    
    
	int rank = 0;
	for (int i = 1; i <= n; i++) {
    
    
		for (int j = i; j <= n; j++) {
    
    
			if (a[j][i]) {
    
    
				swap(a[i], a[j]);
				break;
			}
		}
		if (!a[i][i]) continue;
		else ++rank;
		for (int j = 1; j <= n; j++) {
    
    
			if (a[j][i] && j != i) {
    
    
				a[j] ^= a[i];
			} 
		}
	}
	for (int i = 1; i <= n; ++i) {
    
    
		if (!a[i][n + 1]) continue;
		bool flag = false;
		for (int j = 1; j <= n; ++j) {
    
    
			if (a[i][j]) {
    
    
				flag = true;
				break;
			}
		}
		if (!flag) return 0;
	}
	return 1;
}
vector<int>G[N];
int main() {
    
    
#ifdef _DEBUG
	FILE* _INPUT = freopen("input.txt", "r", stdin);
	//	FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
	rd(n); rd(m);
	while (m--) {
    
    
		int x, y; rd(x), rd(y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	for (int i = 1; i <= n; ++i) {
    
    
		a[i].reset();
		for (auto v : G[i]) a[i][v] = 1;
		if (G[i].size() & 1) a[i][i] = 1;
		else a[i][n + 1] = 1;
	}
	if (!Gauss_rev(n)) puts("N");
	else puts("Y");
	return 0;
}

L. Lavaspar

  • 这题考虑暴力的做即可
  • 对每个单词逐个处理
  • 从上至下从左至右遍历所有点,对于一个点,可以选择四个方向扫描:右边,下边,左下,右下
  • 每次扫描的时候记录好字母出现次数,全部符合便标记扫描的所有位置
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
int l, c, vis[50][50], cnt[50][50];
char a[50][50];
char s[50];
int snum[26], temp[26];
bool check() {
    
    
    for (int i = 0;i < 26;++i)
        if (snum[i] != temp[i]) return false;
    return true;
}
void work(int si,int sj,int len,int flag) {
    
    
    if (flag == 1) {
    
    
        for (int k = sj;k <= sj + len - 1;++k) vis[si][k] = 1;
    }
    else if (flag == 2) {
    
    
        for (int k = si;k <= si + len - 1;++k) vis[k][sj] = 1;
    }
    else if (flag == 3) {
    
    
        for (int k = 0;k < len;++k) vis[si + k][sj + k] = 1;
    }
    else {
    
    
        for (int k = 0;k < len;++k) vis[si + k][sj - k] = 1;
    }
}
int main() {
    
    
    scanf("%d%d", &l, &c);
    for (int i = 1;i <= l;++i)
        scanf("%s", a[i] + 1);
    int n; scanf("%d", &n);
    while (n--) {
    
    
        memset(vis, 0, sizeof vis);
        memset(snum, 0, sizeof snum);
        scanf("%s", s);
        int len = strlen(s);
        for (int i = 0;i < len;++i) snum[s[i] - 'A']++;
        for (int i = 1;i <= l;++i) {
    
    
            for (int j = 1;j <= c;++j) {
    
    
                memset(temp, 0, sizeof temp);
                if (c - j + 1 >= len) {
    
    
                    for (int k = j;k <= j + len - 1;++k) temp[a[i][k] - 'A']++;
                    if (check()) work(i, j, len, 1);
                }
                memset(temp, 0, sizeof temp);
                if (l - i + 1 >= len) {
    
    
                    for (int k = i;k <= i + len - 1;++k) temp[a[k][j] - 'A']++;
                    if (check()) work(i, j, len, 2);
                }
                memset(temp, 0, sizeof temp);
                if (min(c - j + 1, l - i + 1) >= len) {
    
    
                    for (int k = 0;k < len;++k) temp[a[i + k][j + k] - 'A']++;
                    if (check()) work(i, j, len, 3);
                }
                memset(temp, 0, sizeof temp);
                if (min(j, l - i + 1) >= len) {
    
    
                    for (int k = 0;k < len;++k) temp[a[i + k][j - k] - 'A']++;
                    if (check()) work(i, j, len, 4);
                }
            }
        }
        for (int i = 1;i <= l;++i)
            for (int j = 1;j <= c;++j)
                if (vis[i][j]) cnt[i][j]++;
    }
    int ans = 0;
    for (int i = 1;i <= l;++i) {
    
    
        for (int j = 1;j <= c;++j) {
    
    
            if (cnt[i][j]>1) ans++;
        }
    }
        
    printf("%d\n", ans);
    return 0;
}

M. Machine Gun

  • 显然,对于每个点,有两个边界,分别是斜率 k = 1 2 k=\frac{1}{2} k=21 以及 k = − 1 2 k=-\frac{1}{2} k=21, 令 x = 0 x=0 x=0 ,求出对应边界的 y y y
  • 给定一个查询的点 x , y x,y x,y ,求出该点的上界 y 1 = 2 ∗ y − x , y 2 = 2 ∗ y + x y_1=2*y-x,y_2=2*y+x y1=2yx,y2=2y+x
  • 此后便化成了个二维偏序问题,现假设先按照上界排,对于每个上界的 y 1 y_1 y1 值,在这个值当中存放了右边点中上界点 ≤ y 1 \le y_1 y1 的所有点,对这个所有点进行排序后筛选出下界 ≥ y 2 \ge y_2 y2 的点,按照题目的要求操作即可。
  • 问题是该怎么去存上界 ≤ y 1 \le y_1 y1 的所有点,对每一个 y 1 y_1 y1 都存是不现实的,时间不允许,空间也不允许。可以使用主席树来继承。
  • 下面考虑一种方法,使用树状数组来存点,以优化时间和空间。详情看代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, int> pii;
typedef pair<ll, ll>pll;
template<typename T>
inline void rd(T& x)
{
    
    
	int tmp = 1; char c = getchar(); x = 0;
	while (c > '9' || c < '0') {
    
     if (c == '-')tmp = -1; c = getchar(); }
	while (c >= '0' && c <= '9') {
    
     x = x * 10 + c - '0'; c = getchar(); }
	x *= tmp;
}
const int N = 2e5 + 10;
const int M = 1e7 + 10;
const int mod = 1e9 + 7;
const ll inf = 1e16;
const ll NUM = 5782344;
ll gcd(ll x, ll y) {
    
     if (y == 0) return x; return gcd(y, x % y); }
vector<pii>vec[N];
inline int lowbit(int x) {
    
    
	return x & (-x);
}
void add(int x,ll y, int num) {
    
    
	while (x <= N - 10) {
    
    
		vec[x].push_back({
    
     y,num });
		x += lowbit(x);
	}
}
vector<int>get(int x,ll y) {
    
    
	vector<int>ans;
	while (x) {
    
    
		for (auto v : vec[x]) {
    
    
			if (v.first < y) break;
			ans.push_back(v.second);
		}
		x -= lowbit(x);
	}
	return ans;
}
int n, m;
struct node {
    
    
	ll l, r;
	int id;
}a[N];
ll fac[N];
ll b[N];
int main() {
    
    
#ifdef _DEBUG
	FILE* _INPUT = freopen("input.txt", "r", stdin);
	//	FILE* _OUTPUT = freopen("output.txt", "w", stdout);
#endif
	rd(n), rd(m);
	for (int i = 1; i <= n; ++i) {
    
    
		ll x, y; rd(x), rd(y);
		b[i] = a[i].l = 2LL * y - x;
		a[i].r = 2LL * y + x;
		a[i].id = i;
	}
	sort(b + 1, b + 1 + n); int n1 = n;
	n1 = unique(b + 1, b + 1 + n) - b - 1;
	for (int i = 1; i <= n; ++i) {
    
    
		int pos = lower_bound(b + 1, b + 1 + n1, a[i].l) - b;
		add(pos , a[i].r, i);
	}
	for (int i = 0; i <= N - 5; ++i) sort(vec[i].rbegin(), vec[i].rend());
	fac[0] = 1;
	for (int i = 1; i <= N - 5; ++i) fac[i] = fac[i - 1] * NUM % mod;
	ll p = 0;
	while (m--) {
    
    
		ll aa, bb; rd(aa), rd(bb);
		ll x = -1LL - ((p + aa) % mod);
		ll y = (p + bb) % mod;
		ll tmpl = y * 2 - x;
		ll tmpr = y * 2 + x;
		int posl = upper_bound(b + 1, b + 1 + n1, tmpl) - b - 1;
		vector<int>vec1 = get(posl, tmpr);
		sort(vec1.begin(), vec1.end());
		int cnt = 0;
		ll ans = 0;
		for (auto v : vec1) {
    
    
			ans += 1LL * v * fac[cnt++] % mod;
			ans = ans < mod ? ans : ans - mod;
		}
		printf("%lld\n", ans);
		p = ans;
	}
	return 0;
}

N. Number Multiplication

  • 对于这题可以考虑暴力的做法,对于 c i c_i ci 至多 1 0 15 10^{15} 1015 ,进行欧拉筛法就需要进行到 1 0 15 < 31622777 \sqrt{10^{15}}<31622777 1015 <31622777
  • 对于 3 e 7 3e7 3e7 的线性筛法时间还是够的,后面就根据每个数进行质因数分解
  • 因为题目给出的质因子是从小到大排,对每个质因数分解后直接对号入座即可。
  • 为了避免超时加一个优化,使用 s e t set set 将所有质因子的下标装进去,若已经知道该下标的值,则删去从 s e t set set 中删除这个点;若 s e t set set 空了直接跳出。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e3 + 10;
const int INF = 0x3f3f3f3f;
const int M = 31622776 + 10;
inline ll rd() {
    
    
    ll x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
    
    
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
    
    
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
int n, m, k;
int prime[M], cnt = 0;
bool isprime[M];
void init() {
    
    
    for (int i = 2;i <= M - 10;++i) {
    
    
        if (!isprime[i]) prime[++cnt] = i;
        for (int j = 1;j <= cnt && prime[j] * i <= M - 10;++j) {
    
    
            isprime[i * prime[j]] = true;
            if (i % prime[j] == 0) break;
        }
    }
}
ll c[N];
struct node {
    
    
    ll id, cnt;
    node(ll id = 0, ll cnt = 0) :id(id), cnt(cnt) {
    
    }
    bool friend operator<(const node& a, const node& b) {
    
    
        return a.cnt < b.cnt || a.cnt == b.cnt && a.id < b.id;
    }
};
vector<node>vec[N];
vector<node>G[N];
ll p[N];
int main() {
    
    
    init();
 //   scanf("%d %d %d", &m, &n, &k);
    m = rd(), n = rd(), k = rd();
    set<int>s;
    for (int i = 1;i <= m;++i) s.insert(i);
    for (int i = 1;i <= n;++i) c[i] = rd();
    for (int i = 1;i <= k;++i) {
    
    
        int x, y, d;x = rd(), y = rd(), d = rd();
        vec[y].push_back(node(x, d));
    }
    for (int i = 1;i <= n;++i) {
    
    
        ll tmp = c[i];
        G[i].resize(vec[i].size());int pos = 0;
        for (int j = 1;j <= cnt && 1LL * prime[j] * prime[j] <= tmp;++j) {
    
    
            if (tmp % prime[j] == 0) {
    
    
                int tot = 0;
                while (tmp % prime[j] == 0) tmp /= prime[j], ++tot;
                G[i][pos++] = (node(prime[j], tot));
            }
        }
        if (tmp > 1) G[i][pos++] = (node(tmp, 1));
        sort(vec[i].begin(), vec[i].end());
        sort(G[i].begin(), G[i].end());
        for (int j = 0;j < vec[i].size();++j) {
    
    
            p[vec[i][j].id] = G[i][j].id;
            auto v = s.find(vec[i][j].id);
            if (v != s.end()) s.erase(v);
        }
        if (s.empty()) break;
    }
    for (int i = 1;i <= m;++i) printf("%lld%s", p[i], i == m ? "\n" : " ");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/bloom_er/article/details/114986162