SDU-ACM 2020 Winter Camp Day1

题目链接

A Codeforces Round #457 (Div. 2) C. Jamie and Interesting Graph

题意:

给你 n, m, 让你构造一个图出来,n 个点, m 条边,每条边有权重。 没有重边,没有自环。
且从 1 号点到 n 号点最短路的长度是素数。这个图组成的最小生成树的权重和也是素数。

思路:

先挑一个素数 x 出来, x 大于等于 n,
然后我们先连个最短路出来,这个最短路也是最小生成树。
我们把 i 和 i+1 连起来,其中 1 和 2 边的权重为 x - (n - 2) 其他边的权重都设置为 1.
然后我们还剩下m - (n - 1) 条边没有构造,这些边就随便搞搞, 权重设置很大,一定要大于 1 和 2 边的权重, 这样就不会影响最小生成树和最短路。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;

int n,m;
bool vis[N];
vector<int>f;

void solve(){
	vis[1] = 1;
	for (int i = 2; i < N; ++i){
		if (vis[i] == 0) {
			f.push_back(i);
			for (int j = i * 2; j < N;  j+= i)
				vis[j] = 1;
		}
	}
}
int main(){
	solve();
	scanf("%d%d",&n,&m);
	int tmp = lower_bound(f.begin(),f.end(),n) - f.begin();
	tmp = f[tmp];
	int k = tmp * 100;	
	printf("%d %d\n",tmp, tmp);
	for (int i = 1; i < n; ++i){
		if (i == 1) printf("%d %d %d\n",i,i+1,tmp - (n - 2));
		else printf("%d %d 1\n",i,i+1);
	}
	m = m - (n - 1);
	for (int i = 1; i <= n; ++i)
		for (int j = i + 2; j <= n; ++j){
			if (m == 0) return 0;
			printf("%d %d %d\n",i,j,k);
			m--;
		}
	return 0;
}

B Codeforces Round #428 (Div. 2) C. Journey

题意:

给你一个n个节点的树, 从一号节点开始,不断朝邻接点走,直到不能走为止,用 长度 * 走到该点的概率,最后加起来。
每个点只能走一次。

思路:

就跟题意一样的了, 一个dfs就行了

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
vector<int>f[N];
int d[N];
double val[N],ans;
int n,m;
void dfs(int u, int fa){
	int len = f[u].size() - 1;
	d[u]= d[fa] + 1;
	for (int v: f[u]){
		if (v == fa) continue;
		val[v] = val[u] * (1.0/ len);
		dfs(v, u);
	}	
	if (len == 0) ans += val[u] * d[u];
}
int main(){
	int x,y;
	scanf("%d",&n);
	for (int i = 1; i <= n; ++i)
		val[i] = 1;

	for (int i = 1; i < n; ++i){
		scanf("%d%d",&x,&y);
		f[x].push_back(y);
		f[y].push_back(x);
	}
	f[1].push_back(0);
	d[0] = -1;

	dfs(1,0);
	printf("%.6lf\n",ans);
	return 0;
}

C Educational Codeforces Round 57 (Rated for Div. 2) D. Easy Problem

题意:

现在有一个字符串,你需要用ai的钱去掉这个字符串的第i个位置的字符。现在要使得该字符串中不包含子序列hard。求最小钱数。

思路:

用 四个变量 h a r d 分别表示, 到 i 这个位置,
不能组成 h 的价值
不能组成 ha 的价值
不能组成 har 的价值
不能组成 hard 的价值。

  • 当前位置的字符是 h 的时候
    要想不组成 h , 那就一定要把 h 删除掉,
  • 当前位置的字符是 a 的时候
    要想不组成 ha, 要么把前面的 h 删除掉, 要么把当前的字符 a 删除掉
  • 当前位置的字符是 r 的时候,
    要想不组成 har, 要么把前面的 a 删除掉, 那么把当前的字符 r 删除掉,
    a 代表前面组不成 ha , 所以当前字符 r 留着没有事,
    如果不选择删除a, 那就代表前面有可能组成 ha ,但是我把 r 删除掉,同样不能组成 har,
  • 当前字符事 d 的时候,
    要想不组成 hard, 那么把前面的 r 删除掉, 要么把当前的 d 删除掉。
    r 代表前面的不能组成 har, 所以当前的字符 d留着没有事,
    如果不删除 r, 代表前面的有可能组成 har, 所以字符d就必须删除掉。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
char s[N];
int n,m;
long long h, r, a, d;
int main(){
	scanf("%d",&n);
	scanf("%s",s+1);
	for (int i = 1; i <= n; ++i){
		scanf("%d",&m);
		if (s[i] == 'h'){
			h += m;
		} else if (s[i] == 'a'){
			a = min(h, a + m);
		} else if (s[i] == 'r'){
			r = min(a, r + m);
		} else if (s[i] == 'd')
			d = min(r, d + m);
	}
	printf("%lld\n",d);
	return 0;
}

D Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined) C. Travelling Salesman and Special Numbers

题意:

x 操作一次变成 x 的二进制中 1 的个数。
然后问 [1 , n] 中, 有多少个数,操作 k 次正好变成 1.
这个 n 很大, 2的1000次方。

思路:

随便一个数,操作一次之后就会变成小于等于1000 的数, 所以就可以预先处理出来1000以内的数经过几次操作就变成了1.

所以我们做的时候,就先操作一次,然后让他变成一个小于等于1000的数,然后这个数操作 k-1 次变成 1就好了。 记在数组 cnt 中。
然后就可以用排列组合。
从高位到低位进行枚举, 当前位前面已经有了 num 个1.。
当前位是0,那就必须填0, 跳过,
当前位是1, 那就当前位先填0,枚举1000 以内的数 j,如果 cnt[j] == k - 1 那么我们就需要1的个数组成 j 个, 既然前面有num 个1, 那么后面的位置上就有 j - num 个1, 后面的位置上用排列组合计算出来。

有几个特殊情况:

  • 当 k == 0 的时候, 直接输出 1。
  • 当 k == 1 的时候,1 这个数字会被多计算一次, 所以答案要减一。
  • 当枚举到最后一个1的时候, 我们会算当前位是0的情况, 并不会算当前位是1的情况, 所以这个情况也要特判一下。
#include<bits/stdc++.h>
#define popcnt __builtin_popcount
using namespace std;
const int N = 1008;
const int mod = 1e9 + 7;
typedef long long ll;
char s[N];
int k,cnt[N],num;
ll fac[N],inv[N],ans;
ll mul(ll a, ll n, ll p){
	ll ans = 1;
	a %= p;
	while(n){
		if (n & 1) ans = (ans * a) % p;
		a = (a * a) % p;
		n >>= 1;
	}
	return ans;
}
void init(){
	scanf("%s",s);
	scanf("%d",&k);
	for(int i = 2; i < N; ++i)
		cnt[i] = cnt[popcnt(i)] + 1;

	fac[0] = 1;
	for (int i = 1; i < N; ++i)
		fac[i] = (fac[i-1] * i) % mod;
	inv[N-1] = mul(fac[N-1], mod - 2, mod);
	for (int i = N - 2; i >= 0; --i)
		inv[i] = (inv[i+1] * (i + 1)) % mod;
}
ll C(int n, int m){
	if (n < m) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
	init();
	if (k == 0) return puts("1")*0;
	int len = strlen(s);
	for (int  i = 0; i < len; ++i){
		if (s[i] == '0') continue;
		for (int j = max(num, 1); j < len; ++j)
			if (cnt[j] == k - 1) ans = (ans + C(len-i-1,j-num))%mod;
		num++;
	}
	if (k == 1) ans = (ans - 1 + mod) % mod; 
	if (cnt[num] == k-1) ans = (ans + 1)%mod;
	printf("%lld\n",ans);
	return 0;
}

E Codeforces Round #562 (Div. 2) E. And Reachability

题意:

有一个数组 ,n 个数,
询问 x y,
是否可以在 [x, y] 这个区间内按照顺序找几个数出来, 找出来的数中,相邻两个数 & 起来要大于0,
x, y 位置的数必须选择。

思路:

我们有 f[i][j] 代表 从i 这个位置开始, 最小能到达的位置 y ,这个 y 位置上的值 的二进制的 j 位是1,且从 i 位置到 y 位置,满足题目要求。
简单来说就是 区间 [ i, f[i][j] ], 这个区间满足题目的要求,且 f[i][j] 这个位置上的数的二进制的j位是1.

所以我们首要的问题就是预处理出来f[i][j], 从后向前预处理。
明确一个数组的含义 w[j] 代表距离当前位最近的位置上的数的二进制的 j 位为1,

    for (int i = n; i > 0; --i)
        for (int j = 0; j < 25; ++j)
            if ((a[i] >> j) & 1){   //枚举当前数二进制的各个位置上的数是不是1. 
                for (int k = 0; k < 25; ++k)
                    if (w[j] != -1) f[i][k] = min(f[i][k],f[w[j]][k]); //判断是不是有二进制 j 位为1的数。如果有就更新。
                w[j] = i; f[i][j] = min(f[i][j],i);
            }

最后询问的时候。
我们枚举二进制的各个数位 i,
如果 f[x][i] <= y && (a[y] >> i) & 1 说明就满足题目的条件。
也就说明有一个中间位置 T, 这个位置上的数二进制的 i 位 是1, 且 y 位置上的数的二进制的 i 位 也是 1. & 起来肯定大于0.

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+100;
const int INF = 0x3f3f3f3f;
 
int a[N],w[25],f[N][25];
int n,m;
void init(){
    memset(f, INF , sizeof f);
    memset(w , -1 , sizeof w);
    for (int i = 1; i <= n; ++i)
        scanf("%d",&a[i]);
    for (int i = n; i > 0; --i)
        for (int j = 0; j < 25; ++j)
            if ((a[i] >> j) & 1){
                for (int k = 0; k < 25; ++k)
                    if (w[j] != -1) f[i][k] = min(f[i][k],f[w[j]][k]);
                w[j] = i; f[i][j] = min(f[i][j],i);
            }
}

int main(){
    scanf("%d%d",&n,&m);
    init();
    int x,y;
    bool vis;
    for (int i = 0; i < m; ++i){
        scanf("%d%d",&x,&y);
        vis = 0;
        for (int j = 0; j < 25; ++j)
            if ((a[y] >> j)&1 && (f[x][j] <= y)){
                vis = 1; break;
            }
        vis == 1? puts("Shi"): puts("Fou");
    }
    return 0;
}

F Codeforces Round #577 (Div. 2) B. Zero Array

题意:

给你一个数组, n 个数, 每次选择 i j, i != j, 两个位置上的数同时减一。
问最后是不是可以减到全部为0.

思路:

加起来的数是奇数, 不行
最大的数大于 sum / 2 不行, sum 是加起来的总和。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
int n,m;
void dbg() {cout << endl;}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
long long sum,mx;
int main(){
	scanf("%d",&n);
	for (int i = 0; i < n; ++i){
		scanf("%d",&m);
		mx = max(mx,1ll*m);
		sum += m;
	}
	if (sum & 1){
		puts("NO");
		return 0;
	}
	if (mx > sum / 2){
		puts("NO"); 
	} else puts("YES");
	return 0;

}

G VK Cup 2016 - Round 2 E. Little Artem and Time Machine

题意:

n 个操作,
x y z,

  • x == 1 在 y 时间插入一个数 z,
  • x == 2 在 y 时间删除一个数 z。
  • x == 3 在 y 时间询问数 z 出现的次数
思路:

当考虑只有一个数的时候, 那就是简单的树状数组了,

  • 若 x == 1, 那就再 y 的位置上 + 1,
  • 若 x == 2, 那就再 y 的位置上 - 1,
    最后询问就可以了,
    但是这个题插入的数并不一样怎么办呢,
    我们把所有的数据读入,然后按照数的大小排序, 把相同的数聚集再一起, 然后用树状数组, 当一个数用完的时候,把树状数组清空就好了, 由于 y 很大, 所以需要离散化。

还有一种方法就是暴力树状数组了, 用map 当数组。写法更简单。 网上有好多这样的代码。

#include<bits/stdc++.h>
#define low(x) (x & (-x))
using namespace std;
const int N = 1e5+100;
void dbg() {cout << endl;}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}
struct node{
	int a,b,c,id;
	bool operator < (const node &A) const{
		if (c != A.c) return c < A.c;
		return id < A.id;
	}
}e[N];

int n,m,ans[N],c[N];
vector<int>f;
void init(){
	scanf("%d",&n);
	memset(ans, -1, sizeof ans);
	for (int i = 0; i < n; ++i){
		scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
		f.push_back(e[i].b);
		e[i].id = i;
	}
	sort(f.begin(), f.end());
	for (int i = 0; i < n; ++i)
		e[i].b = lower_bound(f.begin(), f.end(), e[i].b) - f.begin() + 1;
	sort(e,e+n);
	// for (int i = 0; i < n; ++i)
	// 	printf("%d %d %d\n",e[i].a,e[i].b,e[i].c);
}

void add(int x, int y){
	for (; x <= n+10; x += low(x))
		c[x] += y;
}
int ask(int x){
	int ans = 0;
	for(; x; x -= low(x))
		ans += c[x];
	return ans;
}
int main(){
	init();
	int l = 0,r;
	while(l < n){
		r = l;
		while(r < n && e[l].c == e[r].c){
			if (e[r].a == 1) add(e[r].b, 1);
			if (e[r].a == 2) add(e[r].b, -1);
			if (e[r].a == 3) ans[e[r].id] = ask(e[r].b);
			r++;
		}
		for (int i = l; i < r; ++i){
			if (e[i].a == 1) add(e[i].b, -1);
			if (e[i].a == 2) add(e[i].b, 1);	
		}
		l = r;

	}
	for (int i = 0; i < n; ++i)
		if (ans[i] != -1) printf("%d\n",ans[i]);
	return 0;
}

H

#include<bits/stdc++.h>
using namespace std;
long long n,m,mx,mn;
int main(){
	scanf("%lld%lld",&n,&m);
	if (m == n || m == 0) mn = 0; else mn = 1;
	if (n >= m * 3)  {
		mx = m * 2;
	} else mx = n - m;
	printf("%lld %lld\n",mn,mx);
	return 0;
}

I

模拟就好了。
注意double 的数不能过大, 不然会丢失精度。 所以每次控制一下double 的大小。

#include<bits/stdc++.h>
using namespace std;
double n,m;
int k;
double solve(double tmp){
	double sum = tmp;
	long long c = (long long) (tmp / n);
	tmp = tmp - c * n;
	int	y = c % 4;

	if (y == 0){
		printf("%.10lf %.10lf\n",tmp, 0.0);
	} else if (y == 1){
		printf("%.10lf %.10lf\n",n, tmp);
	} else if (y == 2){
		printf("%.10lf %.10lf\n",n - tmp,n);
	} else{
		printf("%.10lf %.10lf\n",0.0,n - tmp);
	}
	return sum - 1ll*floor(sum / (n * 4))*4*n;
}
int main(){
	scanf("%lf%lf",&n,&m);
	scanf("%d",&k);
	double tmp = 0;
	for (int i = 1; i <= k; ++i){
		tmp += m;
		tmp = solve(tmp);	
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值