HGOI 20191106

HGOI 20191106

t1 旅行家(traveller)

2s,256MB

【题目背景】

小X热爱旅行,他梦想有一天可以环游全世界……

【题目描述】

现在小X拥有n种一次性空间转移装置,每种装置可以使他前进ai光年,每种装置他拥有bi个。(小X作为一个旅行家,是不会后退的;他的初始坐标是0)

他突然对宇宙的根源感到十分好奇,他发现他用完所有的装置刚好能够到达,于是她就开始了他的旅行。

邪恶的光明法师小S听说了这件事,他决定阻止可爱的小X,于是他使出了扭转乾坤的神通,在前进道路上的m个节点上创造出了可以吞噬一切的黑洞,它们的坐标分别是ci。

小X当然不希望旅行失败,为了嘲讽小S,他想知道他有多少种不同的方式到达宇宙的根源。但是小X也不想过分麻烦你,所以他只想知道答案对100000007取模以后的结果。

注意:每种装置本质相同,即连续使用多次同种装置,但顺序不同算作1种。

【输入格式】

第一行一个正整数n,表示小X拥有的一次性空间转移装置的数量。

接下来n行每行两个正整数ai,bi,分别表示该装置可以使他前进ai光年,他拥有bi个该装置。

接下来一行一个正整数m,表示小S制造出的黑洞数目。

接下来一行m个正整数ci,表示黑洞所在的坐标。

【输出格式】

一行一个正整数,表示答案对100000007取模以后的结果。

【样例输入输出】

traveller.in

2
2 1
3 1
1
1

traveller.out

2

【样例解释】

1号节点存在黑洞,不能通行。

合法的方式为:先使用装置1,再使用装置2;先使用装置2,再使用装置1。

【数据范围】

对于30%的数据,n≤3,bi≤5,m≤3。

对于另外20%的数据,n≤5,bi≤10,ai≤100。

对于另外10%的数据,m=0。

对于另外10%的数据,ai≤100。

对于100%的数据,n≤6,m≤105,0<ci<∑(bi*ai),bi≤12,ai≤109。

题解

显然,所有能被访问到的点是很少的,并且还要求方案,显然一些状态是废的。 我们可以用状态压缩,用 \(f_{a_1 ... a_n}\) 表示当时每一种推进器用了多少个时的方案。

可以使用set<int>表示不能的到达的点。

转移:

\[f_{i,j,k,l,n,m} = f_{i-1,j,k,l,m,n} + f_{i,j-1,k,l,m,n} + f_{i,j,k-1,l,m,n} + f_{i,j,k,l-1,m,n} + f_{i,j,k,l,m-1,n} + f_{i,j,k,l,m,n-1} \]

注意到 \(b_i <= 12\) 最多有 13 种方案, 可以状态压缩。

代码

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define MOD 100000007
using namespace std;
long long reflex[5010000];
long long f[5010000];
set <int> t;
int n, m;
long long a[22];
long long b[22];
long long c[110000];
long long p[20];
void DFS(int x, long long tot, long long prefix){
	f[prefix] = tot;
	if (t.find(tot) != t.end()) reflex[prefix] = -1; // banned
	//cout << prefix << ' ' << f[prefix] << ' ' << reflex[prefix] << endl;
	if (x == n+1) return;
	for (int i=0;i<=b[x];i++){
		DFS(x+1, tot+i*a[x], prefix + p[x] * i);
	}
}
int main(){
	freopen("traveller.in","r",stdin);
	freopen("traveller.out","w",stdout);

	p[1] = 1;
	for (int i=2;i<=13;i++)
		p[i] = p[i-1] * 13;
	cin >> n;
	int fin = 0;
	for (int i=1;i<=n;i++)
		cin >> a[i] >> b[i], fin += b[i] * p[i];
//	cout << fin << endl;
	cin >> m;
	for (int i=1;i<=m;i++)
		scanf("%lld", c+i), t.insert(c[i]);
	DFS(1, 0, 0);
	reflex[0] = 1;
	for (int i=1;i<=fin;i++){
		if (reflex[i] != -1)
		for (int j=1;j<=n;j++){
			if (i % p[j+1] / p[j] != 0 && reflex[i - p[j]] != -1)
				reflex[i] = (reflex[i] + reflex[i - p[j]]) % MOD;
		}
	}
	cout << reflex[fin] << endl;
}

序列(sequence)

2s,512MB

【题目描述】

我们定义一个数对 (x,y)是好的,当且仅当 x≤y,且 x xor y 的二进制表示下有奇数个1。

现在给定 n个区间 [li,ri],你需要对于每个i∈[1,n],输出有几对好的数 (x,y) 满足 x 和 y 都在[l1,r1]∪[l2,r2]...∪[li,ri],即两个数都在前 i 个区间的并里。

【输入格式】

第一行一个正整数 n

接下来 n 行每行两个整数[li,ri],表示第 i 个区间,保证 li≤ri。

【输出格式】

输出 n 行,第 i 行一个整数表示有几对好的数 (x,y) 满足 x,y 都在前 i个区间的并里

【样例输入】

3
1 7
3 10
9 20

【样例输出】

12
25
100

【数据范围】

对于30%的数据,1≤n≤100,1≤li≤ri≤100。

对于50%的数据,1≤n≤1000。

对于100%的数据,1≤n≤100000,1≤li≤ri≤2^31-1。

题解

不妨令 \(T(x)\) 表示 \(x\) 的二进制 1 的个数。

显然两个数 \(a,b\) 异或起来时,\(T(a \mathrm{xor} b) \mod 2 = (T(a) + T(b)) \mod 2\)

所以答案为

\[\mathrm{ans} = \sum_{i,j \in A} [T(i + j) \mod 2 = 1] \\ = \sum_{i,j \in A} [T(i) \mod 2 + T(j) \mod 2 = 1] \\ = \sum_{i} [T(i) \mod 2 = 1] \sum_{j} [T(i) \mod 2 = 0] \]

分别计算 \(\sum_{i} [T(i) \mod 2 = 1]\)\(\sum_{j} [T(i) \mod 2 = 0]\) 即可。

对于区间并,可以离线后用map<int,int> 维护,每一次遇到可行线段删除即可。

代码

#include<bits/stdc++.h>
using namespace std;
long long int reflex[201000]; // reflexing the numbers l, r token
bool vis[201000];
long long int l[101000];
long long int r[101000];
int n;
map <long long int, int> p;
long long odd, even;
// we assume that [l, r)
void calc(int p){ // calc numbers between [ reflex[p], reflex[p+1] )
	long long int beg = reflex[p];
	long long int end = reflex[p+1];
	if (beg & 1){
		if (bitset<50>(beg).count() & 1) odd ++;
		else even ++;
		beg ++;
	}
	if (beg > end) return;
	odd += (end - beg) / 2;
	even += (end - beg) / 2;
	if ((end - beg) & 1){
		if (bitset<50>(end-1).count() & 1) odd ++;
		else even ++;
	}
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	cin >> n;
	for (int i=1;i<=n;i++){
		scanf("%lld%lld",l+i,r+i);
		r[i] ++;
		p[l[i]]; p[r[i]];
	}
	int pl = 0;
	typedef map<long long int, int>::iterator Iter;
	for (Iter it = p.begin(); it != p.end(); it ++)
		it -> second = ++pl, reflex[pl] = it -> first;
	for (int i=1;i<=n;i++){
		Iter beg = p.lower_bound(l[i]);
		Iter end = p.lower_bound(r[i]);
		for (Iter it = beg; it != end; it ++)
			calc(it -> second);
		while (beg != end){
			Iter cpy = beg;
			beg ++;
			p.erase(cpy);
		}
		printf("%lld\n", odd * even);
	}
}

钢琴家(pianist)

1s,256MB

【题目背景】

一年一度的维也纳年度音乐会即将拉开帷幕,由于小X是举世闻名的钢琴家,本次音乐会的导演小Y邀请小X出场演出……

【题目描述】

小X是一位钢琴大师,他对音乐有着自己独特的见解。

维也纳年度音乐会的导演小Y邀请小X参加今年的维也纳年度音乐会,并把自己创作的一张含有S个音符的谱子交给小X,希望他可以在音乐会上弹奏。

由于小X对音乐有着自己独特的见解,他只喜欢n个音乐片段,为了使他在维也纳年度音乐会上可以发挥出最佳水平,他希望他弹奏的谱子是由他喜欢的音乐片段构成的。

但小X又是一个人见人爱、花见花开的通情达理的好孩子,他不希望他对原谱子的改动过多而使小Y伤心难过。由于小X是钢琴大师,日理万机,他没有足够的时间来钻研他最少修改原谱子的多少个音符就可以使原谱子是由他喜欢的音乐片段构成的。所以他希望你直接把修改好后的谱子交给他。

我们假设谱子的音符范围在0-9之间,你需要修改最少的音符,使得原谱子可以分成m个片段,每一个片段小X都喜欢。

【输入格式】

第一行一个长度为S的数字串,表示原谱子。

第二行一个正整数n,表示小X喜欢的片段个数。

接下来n行每行一个数字串,表示小X喜欢的片段。

最后一行10个正整数a[1]-a[10],表示评分标准,当你修改的音符个数<=a[i]时,你可以得到i分。

【输出格式】

m行每行一个片段(请按照原数字串顺序输出这m个片段),为修改后的答案。

pianist.in

12341122334
3
12
34
123
11 10 9 8 7 6 5 4 3 2

pianist.out

12
34
12
123
34

题解

考虑动态规划。

\(f_{i}\) 表示在第 \(i\) 的位置时的最优解,普通转移即可(莫名其妙的)。

代码

#include<bits/stdc++.h>
using namespace std;
string s;
string st[110];
string ans[1100];
int f[1100];
int opt[1100];
int calc(int nx, string &t){
	int len = t.length();
	int ret = (nx + 1 == len ? 0 : f[nx - len]);
	for (int i=nx-len+1, j=0;j<len;i++, j++){
		if (s[i] != t[j]) ret ++;
	}
	return ret;
}
int main(){
	freopen("pianist.in","r",stdin);
	freopen("pianist.out","w",stdout);

	cin >> s;
	int S = s.length();
	int n;
	cin >> n;
	for (int i=1;i<=n;i++){
		cin >> st[i];
	}
	memset(f, 0x3f, sizeof(f));
	for (int i=0;i<S;i++){
		for (int j=1;j<=n;j++){
			int len = st[j].length();
			if (len <= i + 1){
				if (calc(i, st[j]) < f[i]){
					f[i] = calc(i, st[j]);
					opt[i] = j;
				}
			}
		}
	}
	int pl = S-1;
	int nx = 0;
	while (pl != -1)
	{
		ans[++nx] = st[opt[pl]];
		pl -= st[opt[pl]].length();
	}
	while (nx){
		cout << ans[nx] << endl;
		nx --;
	}
}
posted @ 2019-11-06 13:46  dgklr  阅读(190)  评论(0编辑  收藏  举报