Vjudge 3.14 训练解题报告

比赛传送门 password:3.1415926

A. Fibonacci-ish

题意:定义一个序列为“Fibonacci-ish”的,当且仅当对任意 2<in,ai=ai1+ai2。给定一个长为 n 的数组,求选出若干个元素重新排列,形成“Fibonacci-ish”的序列的最长长度。n1000,V109

首先有一个暴力做法,n2 枚举两个起点,然后后面的元素都可确定。思考可以发现,这个暴力其实是正确的,因为斐波那契数的增长速度是指数级的,自然长度是 log 级的,所以暴力跑的复杂度为 O(n2log2n)。需要注意特判两个起点均为 0 的情况,此时答案为 cnt0

By zhouhaoxu

#include<bits/stdc++.h>
#define int long long
#define N 200005
#define pb push_back
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
int n,a[N],ans; map<int,int>cn; vector<int>vi;
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),cn[a[i]]++,ans+=(a[i]==0);
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
		if(i==j) continue; if(a[i]==0&&a[j]==0) continue; vi.clear(),vi.pb(a[i]),vi.pb(a[j]);
		cn[a[i]]--,cn[a[j]]--; int p=a[i]+a[j],ret=2,lst=a[j]; while(p<=(int)2e9&&p>=(int)-2e9&&cn[p]){
			cn[p]--,vi.pb(p); int t=lst+p; lst=p,p=t,++ret;
		}for(int &u:vi) cn[u]++; ans=max(ans,ret);
	}return !printf("%lld\n",ans);
}

B. Grime Zoo

题意:给定一个含问号的 01 串,你需要将所有问号改为 0 或 1,定义代价为 01 的子序列×x 加上 10 的子序列×y。最小化代价。n105

直接考虑非常复杂,所以考虑挖掘性质。可以猜想,如果 01 的代价较大,则要尽可能把前面填 1,后面填 0;反之则尽可能前面填 0,后面填 1。

这种贪心看起来比较假,但实际上不难证明为真:假设 01 的代价 x 较大,使用反证法,假设最优方案中有一个问号填 0 在填 1 前面,证明交换此 01 更优:

更感性一点的证明是,交换两位对左右两边来说没有影响,因为在他们看来,都是同一侧有一个 0 一个 1,交换位置没有影响。但对中间来说,一个是左 0 右 1,一个是左 1 右 0,自然后者更优。

于是,我们假设要左边填 1 右边填 0(反之则 01 取反即可),考虑如何计算答案。首先预处理出数字与数字之间的贡献 sum(即不考虑问号),然后预处理一些必要信息(如前缀 0,1,? 的个数 s0(i),s1(i),s?(i))。接下来,使用递推的方式处理出从 1 走到 i 全填 1 在左边的贡献 l(i),以及从 n 走到 i 全填 0 在右边的贡献 r(i)。最后枚举分界线,答案为 sum+l(i)+r(i+1)+?10+?01

预处理 l(i),r(i) 的原因是,左边的 ?1 对右边的 0 的贡献容易计算,因为右边所有的 0 都在 ?1 的右侧,但左边的 ?1 对左边的 0 的贡献较难直接计算,因为不同的 ?1 左边 0 的个数并不相同,需要递推处理。

By cxm1024

#include <bits/stdc++.h>
using namespace std;
#define int long long
int s1[100010], s0[100010], s2[100010];
int l[100010], r[100010];
signed main() {
	string s;
	int x, y;
	cin >> s >> x >> y;
	int n = s.size();
	s = " " + s;
	if (y > x) {
		for (int i = 1; i <= n; i++) {
			if (s[i] == '1') s[i] = '0';
			else if (s[i] == '0') s[i] = '1';
		}
		swap(x, y);
	}
	for (int i = 1; i <= n; i++) {
		s1[i] = s1[i - 1] + (s[i] == '1');
		s0[i] = s0[i - 1] + (s[i] == '0');
		s2[i] = s2[i - 1] + (s[i] == '?');
	}
	int res = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == '1')
			res += s0[i - 1] * x + (s0[n] - s0[i]) * y;
	for (int i = 1; i <= n; i++) {
		l[i] = l[i - 1];
		if (s[i] == '?') l[i] += s0[i - 1] * x + (s0[n] - s0[i]) * y;
	}
	for (int i = n; i > 0; i--) {
		r[i] = r[i + 1];
		if (s[i] == '?') r[i] += (s1[n] - s1[i]) * x + s1[i - 1] * y;
	}
	int ans = r[1];
	for (int i = 1; i <= n; i++) {
		if (s[i] == '?') ans = min(ans, l[i] + r[i + 1] + s2[i] * (s2[n] - s2[i]) * y);
	}
	cout << ans + res << endl;
	return 0;
}

C. Vasily the Bear and Sequence

题意:给一个长度为 n 的数组,你需要选出一些元素,使他们按位与后的 lowbit 尽量大(定义 lowbit(0)=-1),在此基础上元素尽可能多。n105,V109,VV

显然可以枚举钦定 lowbit,然后判定是否可行。如果 lowbit 钦定为第 i 位,则选的元素的第 i 位只能为 1(一旦有 0 就会被与成 0)。然后就是要让更低的位变成 0,容易发现与的元素越多越容易变成 0,所以最优一定是把所有第 i 位为 1 的元素全部选上。

By cxm1024

#include <bits/stdc++.h>
using namespace std;
int n, a[100010];
signed main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	vector<int> ans;
	int maxn = -1;
	for (int i = 0; i <= 30; i++) {
		vector<int> v;
		for (int j = 1; j <= n; j++)
			if (a[j] >> i & 1) v.push_back(a[j]);
		if (v.empty()) continue;
		int res = v[0];
		for (int j : v) res &= j;
		if ((res & -res) > maxn)
			ans = v, maxn = (res & -res);
	}
	printf("%lu\n", ans.size());
	for (int x : ans) printf("%d ", x);
	return 0;
}

D. Mike and Frog

题意:有两个变量,第 0 秒为 h1,h2,每秒 h1x1h1+y1modm,h2x2h2+y2modm,问最早使 h1=a1 h2=a2 的时刻是多少。m106,0h,a,x,y<m,ha

显然问题的关键在于形成的环。两个元素都会在若干步之后进入环,所以可以暴力跑 m 步,处理出进入之前的长度和环的长度,同时顺便处理好了目标位置不在环上的情况。以下只需要考虑目标在环上。

假设两个环长分别为 len1,len2,目标在环上的位置分别为 b1,b2,环之前的长度为 t1,t2,则两个变量同时满足要求的时刻 x 满足:

{xt1b1(modm)xt2b2(modm)

看起来需要使用 exgcd 来解同余方程,但其实并不需要,可以如下操作:

因为 xt1b1(modm),所以 x=km+b1+t1。感性理解一下可以发现,随着 k 的变化,在第二个同余式中的结果同样会形成一个不超过 m 的环。于是直接 O(m) 枚举 k 检验是否合法即可。

By zhouhaoxu

#include<bits/stdc++.h>
#define int long long
#define N 2000005
#define pb push_back
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
int l[2],m,h[2],a[2],x[2],y[2],p[2],t[N],vs[N],ls[N],th[2];
signed main(){
	scanf("%lld",&m); for(int i=0;i<2;i++)
		scanf("%lld%lld%lld%lld",&h[i],&a[i],&x[i],&y[i]),th[i]=h[i];
	for(int t=0;t<=m;t++){
		if(th[0]==a[0]&&th[1]==a[1]) return !printf("%lld\n",t);
		for(int i=0;i<2;i++) th[i]=(th[i]*x[i]+y[i])%m;
	}for(int i=0;i<2;i++){
		memset(vs,0,sizeof(vs)),memset(t,0,sizeof(t));
		int u=h[i]; while(!vs[u]) t[u]=(l[i]++),vs[u]=1,u=(x[i]*u+y[i])%m;
		int va=u; u=h[i]; ls[i]=t[va];
		for(int tp=0;tp<t[va];tp++) l[i]--,vs[u]=0,u=(x[i]*u+y[i])%m;
		if(!vs[a[i]]) return !printf("-1"); p[i]=t[a[i]]-t[va];
	}for(int i=0;i<=l[1];i++) if((l[0]*i+p[0]+ls[0]-ls[1])%l[1]==p[1])
		return !printf("%lld\n",(l[0]*i+p[0]+ls[0]-ls[1]));
	return !printf("-1");
}

E. Hidden Word

题意:一个 2×13 的矩阵,每个格子填一个字母。两个格子相邻按照八连通判定。现给定一个长度 27 的字符串,且每个字母至少出现一次,构造一个填字母的方法,使该字符串能对应矩阵中一个路径。

显然问题的关键在于那个重复的字母如何经过两次。观察样例可以发现,可以如下构造:

此时该字母的两次出现之间隔了偶数个字母。如果隔了奇数个,可以如下构造:

对于左边的填法,字母多的行绕到另一行补齐一下即可。

By cxm1024

#include <bits/stdc++.h>
using namespace std;
bool vis[26];
char a[2][13];
signed main() {
	string s;
	cin >> s;
	char now = 'A', flag;
	for (int i = 0; i < s.size(); i++) {
		if (!vis[s[i] - 'A'])
			vis[s[i] - 'A'] = s[i];
		else flag = s[i];
	}
	vector<int> v;
	for (int i = 0; i < s.size(); i++)
		if (s[i] == flag) v.push_back(i);
	if (v[0] + 1 == v[1]) puts("Impossible");
	else {
		int x = 0, y = 13 - (v[1] - v[0] - 1) / 2;
		a[x][y - 1] = flag;
		if (y == 13) y--, x++;
		for (int j = v[0] + 1; j < v[1]; j++) {
			a[x][y] = s[j];
			if (x == 0) y++;
			else y--;
			if (y == 13) y--, x++;
		}
		a[0][13 - (v[1] - v[0] - 1) / 2 - 2] = flag;
		x = 0, y = 13 - (v[1] - v[0] - 1) / 2 - 2;
		if (y < 0) y = 0, x = 1;
		for (int j = v[1] + 1; j < s.size(); j++) {
			a[x][y] = s[j];
			if (x == 0) y--;
			else y++;
			if (y < 0) x++, y = 0;
		}
		x = 1, y = 13 - (v[1] - v[0]) / 2 - 1;
		if (y < 0) y = 0, x = 0;
		for (int j = v[0] - 1; j >= 0; j--) {
			a[x][y] = s[j];
			if (x == 1) y--;
			else y++;
			if (y < 0) x--, y = 0;
		}
		for (int i = 0; i <= 1; i++) {
			for (int j = 0; j < 13; j++)
				cout << a[i][j];
			cout << endl;
		}
	}
	return 0;
}

F. Video Cards

题意:有 n 个元素,你可以选一个元素作为中心元素,其他元素中选若干个作为附属元素,要求附属元素必须为中心元素的倍数,否则可以通过减小附属元素来调整至合法。注意中心元素不能调整。求和最大的合法方案。n,V2×105

容易发现附属元素必然可以全选(最差也可以减少到 0),且把中心元素当成附属元素处理也可以(自己是自己的倍数),于是问题转化为,选一个中心元素 x,使得 i=1nai(aimodx) 最大。进一步转化为最大化 i=1naixx=xi=1naix

容易发现在 [kx,(k+1)x) 内的所有元素 yyx 的值都为 k,于是对于一个 x,本质不同的值最多只有 n/x 个,所以可以调和级数计算答案。预处理值域上的前缀和,复杂度 O(Vlog(V))

By cxm1024

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, a[200010], s[200010];
signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		s[a[i]]++;
	}
	for (int i = 1; i <= 200000; i++)
		s[i] += s[i - 1];
	long long ans = 0;
	for (int i = 1; i <= 200000; i++) {
		if (s[i] - s[i - 1] == 0) continue;
		long long res = 0;
		for (int j = i; j <= 200000; j += i)
			res += (s[min(200000ll, j + i - 1)] - s[j - 1]) * (j / i);
		ans = max(ans, 1ll * i * res);
	}
	printf("%lld\n", ans);
	return 0;
}

G. Masha-forgetful

题意:给定 n 个模式串 s1sn 和一个文本串 t,长度均为 m,字符集为 09。定义一个 t 的子串合法,当且仅当长度 2 且在某个 s 中出现过。构造将 t 划分为合法子串的方案或判断无解。T104,nm106

显然对于一个合法的字符串,其所有长度 2 的子串也合法。于是有一个非常重要的结论:t 的划分长度要么为 2 要么为 3。于是可以暴力预处理出模式串中出现过的所有长度为 23 的字符串,暴力 DP 即可。

By zhouhaoxu

#include<bits/stdc++.h>
#define N 200005
#define pb push_back
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
int f[N],T,n,m,pr[N]; map<string,pii >st;
bool is(string s){return st.find(s)!=st.end();}
struct node{int l,r,k;}; vector<node>ans;
void calc(string u){
	pii p=st[u]; ans.pb((node){p.se,p.se+(int)u.size()-1,p.fi});
}string s,t; signed main(){
	ios::sync_with_stdio(false),cin>>T;
	while(T--){
		cin>>n>>m,st.clear(),ans.clear();
		for(int i=1;i<=n;i++){
			cin>>s; for(int j=0;j<m;j++){
				if(j+1<m) st[s.substr(j,2)]=make_pair(i,j);
				if(j+2<m) st[s.substr(j,3)]=make_pair(i,j);
			}
		}cin>>t; for(int i=0;i<=m;i++) f[i]=pr[i]=0;
		if(m==1){cout<<-1<<endl; continue;}
		if(m>1) f[1]=is(t.substr(0,2));
		if(m>2) f[2]=is(t.substr(0,3));
		for(int i=1;i<m;i++){
			if(!f[i]) continue;
			if(i+2<m&&is(t.substr(i+1,2))) f[i+2]=1,pr[i+2]=i;
			if(i+3<m&&is(t.substr(i+1,3))) f[i+3]=1,pr[i+3]=i;
		}if(!f[m-1]) cout<<-1<<endl; else{
			int r=m-1;
			while(r>2) calc(t.substr(pr[r]+1,r-pr[r])),r=pr[r];
			calc(t.substr(0,r+1)); reverse(ans.begin(),ans.end());
			cout<<(int)ans.size()<<endl;
			for(auto &u:ans) cout<<u.l+1<<' '<<u.r+1<<' '<<u.k<<endl;
		}
	}return 0;
}

如果没有注意到段长必须为 2 或 3,其实也可以做。设 fi 表示是否能将前 i 个字符划分成合法的段,则 fi=fjfj+1fi1,其中 j 为最小的使 t[j+1,i] 合法的位置。显然转移可以使用前缀和优化,所以只需要找到最小的合法的 j 即可。

要对每个位置找到最长的出现过的后缀,可以使用 SAM 维护。首先将 s1#s2##sn 建 SAM,则其中维护了所有 s 的子串信息。每次尝试在当前节点添加一个字符,如果不能转移则在 parent tree 上跳 fa,直到能转移,此时即为最长后缀。

注意转移时 ji1(长度至少为 2),特殊处理一下即可。

By cxm1024

#include <bits/stdc++.h>
using namespace std;
string t;
struct node {
	int fa, nxt[11], len, tag, r;
	node() {
		fa = len = tag = r = 0;
		memset(nxt, 0, sizeof(nxt));
	}
} a[2000010];
int lst = 1, cnt = 1;
void insert(int x, int y, int z) {
	int p = lst, now = ++cnt;
	a[now] = node();
	a[now].len = a[lst].len + 1, a[now].tag = y, a[now].r = z;
	for (; p && a[p].nxt[x] == 0; p = a[p].fa)
		a[p].nxt[x] = now;
	int q = a[p].nxt[x];
	if (q == 0) a[now].fa = 1;
	else if (a[p].len + 1 == a[q].len) a[now].fa = q;
	else {
		int r = ++cnt;
		a[r] = node();
		a[r] = a[q], a[r].len = a[p].len + 1;
		for (; p && a[p].nxt[x] == q; p = a[p].fa)
			a[p].nxt[x] = r;
		a[q].fa = a[now].fa = r;
	}
	lst = now;
}
int f[1010], g[1010], pre[1010];
int rr[1010], ii[1010];
void Solve(int test) {
	lst = cnt = 1, a[1] = node();
	int n, m;
	cin >> n >> m;
	for (int i = 0; i <= m; i++) {
		f[i] = g[i] = 0;
		pre[i] = rr[i] = ii[i] = 0;
	}
	for (int i = 1; i <= n; i++) {
		string s;
		cin >> s;
		for (int j = 0; j < s.size(); j++)
			insert(s[j] - '0', i, j + 1);
		insert(10, i, 0);
	}
	cin >> t;
	t = " " + t;
	int now = 1, l = 1;
	if (a[now].nxt[t[1] - '0']) now = a[now].nxt[t[1] - '0'];
	else l = 2;
	for (int i = 2; i <= m; i++) {
		while (now != 1 && a[now].nxt[t[i] - '0'] == 0)
			l = i - a[a[now].fa].len, now = a[now].fa;
		g[i] = g[i - 1];
		if (a[now].nxt[t[i] - '0'] == 0) l = i + 1;
		if (a[now].nxt[t[i] - '0']) {
			now = a[now].nxt[t[i] - '0'];
			if (l == 1) {
				f[i] = 1, pre[i] = 0;
				rr[i] = a[now].r, ii[i] = a[now].tag;
				g[i] = i;
			}
			else if (g[i - 2] >= l - 1) {
				f[i] = 1, pre[i] = (i == 0 ? -1 : g[i - 2]);
				rr[i] = a[now].r, ii[i] = a[now].tag;
				g[i] = i;
			}
		}
	}
	if (!f[m]) cout << -1 << endl;
	else {
		vector<array<int, 3> > ans;
		int now = m;
		while (now) {
			ans.push_back({rr[now] - (now - pre[now]) + 1, rr[now], ii[now]});
			now = pre[now];
		}
		reverse(ans.begin(), ans.end());
		cout << ans.size() << endl;
		for (auto [x, y, z] : ans)
			cout << x << " " << y << " " << z << endl;
	}
	while (cnt) a[cnt--] = node();
	for (int i = 0; i <= m; i++) {
		f[i] = g[i] = 0;
		pre[i] = rr[i] = ii[i] = 0;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	for (int _test = 1; _test <= T; _test++)
		Solve(_test);
	return 0;
}

H. Nearest Fraction

题意:给定一个分数 xy,求一个分数 ab 使得 bn,且与 xy 尽可能接近。如有相同则取较小的 abx,y,n105

由于 n 很小,可以枚举分母。对于一个分母来说,可以通过二分答案来确定最接近的分子。在此题中 double 的精度足够通过。

By zhouhaoxu

#include<bits/stdc++.h>
#define int long long
#define N 200005
#define pb push_back
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
int x,y,n,a0=-1,b0=-1; double ans=1;
signed main(){
	scanf("%lld%lld%lld",&x,&y,&n);
	for(int i=1;i<=n;i++){
		int l=0,r=(int)1e10,ret=0; while(l<=r){
			int md=(l+r)>>1; if(md*y<=i*x) ret=md,l=md+1;
			else r=md-1;
		}for(int j=max(ret-2,0ll);j<=ret+2;j++){
			double va=fabs(1.0*x/y-1.0*j/i);
			if(a0==-1){ans=va,a0=j,b0=i; continue;}
			if(ans-va>1e-15) ans=va,a0=j,b0=i;
			else if(fabs(ans-va)<1e-15){
				if(b0>i) b0=i,a0=j; else if(b0==i) a0=min(a0,j);
			}
		}
	}return !printf("%lld/%lld\n",a0,b0);
}

事实上,最接近的分子可以直接得出,而无需二分答案。当 abxy 时,有 abxy。所以只需要在该值附近枚举一两个相邻元素即可。

By AmirAz

#include <iostream>
#include <cstdio>
#include <set>
#include <cmath>
 
using namespace std;
typedef long long ll;
 
bool comp(ll a, ll b, ll x1, ll y1, ll x2, ll y2){
    return (abs(a * y1 * y2 - x1 * b * y2) < abs(a * y2 * y1 - x2 * b * y1));
}
 
int main(){
 
    ll a, b, n; cin >> a >> b >> n;
    ll minx = 1e7 + 10, miny = 1;
 
    for (ll i = 1; i <= n; i++){
        ll y = i;
 
        ll x = floor((long double) y * a / b);
        if (comp(a,b, x, y, minx, miny)){
            minx = x;
            miny = y;
        }
 
        x = ceil((long double)y * a / b);
        if (comp(a,b, x, y, minx, miny)){
            minx = x;
            miny = y;
        }
 
    }
 
    cout << minx << "/" << miny << endl;
 
}
posted @   曹轩鸣  阅读(143)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起