CF1835 题解
CF1835 题解
A
考虑到 \(a,b,c > 3\) 的最多只有 \(5\) 个,可以直接循环枚举 \(A\) 。首先将 \(B\) 取到最小值,然后判断 \(C\) 是否达到了最小值,如果没有就将 \(B\) 和 \(C\) 同时补齐。此时特判如果 \(C\) 大于上限(就是 \(\geq 10^{c}\) ),就不对答案贡献。不然在 \(A\) 一定的状态下,可以将 \(B,C\) 同加,作出的贡献就是 \(\min\ \{maxb\ -\ b,maxc\ -\ c\}\) 。如果 \(k\) 不够了就补齐输出即可。
按上述过程模拟,注意细节。
#include<bits/stdc++.h>
using namespace std;
int a,b,c,A,B,C,maxc,maxb,maxa;
long long k;
inline int pow(int x,int tms)
{
int ret = 1;
for(int i = 1;i <= tms;i++) ret *= x;
return ret;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>a>>b>>c>>k;
maxa = pow(10,a); maxb = pow(10,b); maxc = pow(10,c);
if(max(a,b) < c - 1){puts("-1");continue;}
for(A = maxa / 10;A < maxa;A++)
{
B = maxb / 10;
C = A + B;
if(C < maxc / 10){B += maxc / 10 - C;C = maxc / 10;}
if(C >= maxc) break;
if(B >= maxb) continue;
if(k > min(maxc - C,maxb - B)) k -= min(maxc - C,maxb - B);
else
{
B += k - 1; C += k - 1;
cout<<A<<" + "<<B<<" = "<<C<<endl;
k = 0;
break;
}
}
if(k) puts("-1");
}
return 0;
}
B
如果我们确定了最后一个人选的位置 \(x\) ,就可以用一个函数 \(O(\log n)\) 计算出可以中奖的号码对应的区间。方法是二分出左右两边分别到 \(x\) 这一段包含 \(k\) 个点(含自身)的个数。那么对于左边,中奖号码能取的区间就是 \(x\) 和左端点的中点右取整,右边同理。
考虑我们不能枚举每一个 \(x\) 的位置,哪些位置才是有用的。结论是,每个被别人选过的点 \(-2,-1,0,+1,+2\) 。考虑讨论一下两个相邻的别人选的位置,中奖号码是在两个左边,中间,还是两个右边。可以发现不管是哪里靠近两端都是不劣的,\(+2\) 是因为如果中奖号码取到某个特殊的位置,最后一个人取 \(+1\) 时可能和右边的人并列,导致输掉。所以可以 \(+2\) 来规避这个问题。
从左向右询问 \(x\) 可以用双指针完成。时间复杂度 \(O(n\log n)\) 。瓶颈在于排序。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
ll n,m,k,pm = 1,px = 1;
ll a[N];
inline ll calc(ll x)
{
while(pm <= n && a[pm] < x) ++pm;
while(px <= n && a[px] <= x) ++px;
ll l,r;
l = ((px - k > 0) ? x - (x - a[px - k] + 1) / 2 + 1: 0);
r = ((pm + k - 1 <= n) ? x + (a[pm + k - 1] - x + 1) / 2 - 1 : m);
return max(0ll,r - l + 1);
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i = 1;i <= n;i++) cin>>a[i];
a[n + 1] = m + 1;
sort(a + 1,a + n + 1);
ll ans = calc(0),mxpos = 0;
for(ll i = 1,nl,nr;i <= n;i++)
{
if(i == 1) nl = max(0ll,a[i] - 2);
else nl = max(a[i] - 2,a[i - 1] + 3);
nr = min(m,a[i] + 2);
for(ll j = nl;j <= nr;j++)
{
ll now = calc(j);
if(now > ans) ans = now,mxpos = j;
}
}
cout<<ans<<" "<<mxpos;
return 0;
}
C
我们发现数据十分特殊,异或前缀和一下变成是否有 \(4\) 个数 \(a,b,c,d\) ,满足 \(a \ xor \ b = c \ xor\ d\) 。我们将数拆分成二进制前 \(k\) 位和后 \(k\) 位。前 \(k\) 位有至多 \(2^k\) 种数字,我们有 \(2^{k + 1} + 1\) 个前缀(包括 \(0\))。如果我们对于前缀 \(i\) ,找到一个前缀 \(j\) ,两个的前 \(2^k\) 位相等,那么 \([i,j]\) 的异或和就只有后 \(k\) 位。我们先将前面的可能填满,后面还有 \(2^k + 1\) 个前缀,对于后 \(k\) 位就有 \(2^k + 1\) 个取值。因为后 \(k\) 位只有 \(2^k\) 个取值,所以一定能匹配到一个取值,使得后 \(k\) 位异或为 \(0\) ,这样就找到了答案。
所以并不存在 \(-1\) 的情况,异或和 \(hash\) 一下就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 5;
int n,m,jdg = 0;
ll a[N],pre[N],vis[N];
map <ll,pair<int,int> > p;
inline void calc(int l,int r)
{
if(p.find(a[l - 1] ^ a[r]) == p.end())
{
p[a[l - 1] ^ a[r]] = make_pair(l,r);
return;
}
pair <int,int> res = p[a[l - 1] ^ a[r]];
if(res.second < l) cout<<res.first<<" "<<res.second<<" "<<l<<" "<<r<<endl;
else if(res.first <= l && l <= res.second) cout<<res.first<<" "<<l - 1<<" "<<res.second + 1<<" "<<r<<endl;
else if(res.first > l) cout<<l<<" "<<res.first - 1<<" "<<res.second + 1<<" "<<r<<endl;
jdg = 1;
}
int main()
{
int T;
cin>>T;
while(T--)
{
p.clear();
cin>>n; jdg = 0;
m = (1 << (n + 1));
for(int i = 1;i <= m;i++) cin>>a[i];
for(int i = 2;i <= m;i++) a[i] ^= a[i - 1];
for(int i = 0;i <= m / 2;i++) vis[i] = -1;
vis[0] = 0;
for(int i = 1;i <= m;i++)
{
if(vis[(a[i] >> n)] != -1) calc(vis[(a[i] >> n)] + 1,i);
vis[(a[i] >> n)] = i;
if(jdg) break;
}
}
return 0;
}
D
首先缩点,将原图缩成一些 \(SCC\) 。考虑如果想用每个 \(SCC\) 内部的环绕出一个距离的话,每个环最多绕 \(n\) 次,最多有 \(n\) 个环,环长最多为 \(n\) 。所以当 \(k \geq n^3\) 时,如果有解,一定能绕出来。 \(x,y\) 能互相到达,一定在一个 \(SCC\) 内。
由裴蜀定理可得,一些环长能凑出的长度是 \(gcd\) 的倍数。想要知道每一个环的信息,考虑构造生成树,每条非树边都能贡献一个最小环。由于是在每个 \(SCC\) 内分别 \(dfs\) ,所以没有横叉边,只有返祖边,所以环长等于 \(|dep_u - dep_v - 1|\) 。
考虑满足条件的 \(x,y\) ,满足 \(dep_u - dep_y \equiv dep_v - dep_i \equiv k\ (\mod d)\) 。
所以 \(k \equiv 0 \ (\mod d)\) 或者 \(k \equiv \frac d2(\mod d)\) ,第二种情况 \(d\) 为偶数。
开桶分别计算即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
struct Edge{
int v,next;
}e[N * 2];
typedef long long ll;
ll k;
int d,n,m,head[N],dfn[N],vis[N],cnt = 0,low[N],belong[N],tot = 0,top = 0,s[N],dep[N],pot[N];
vector <int> pts[N];
inline void add(int x,int y)
{
++tot;
e[tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
inline void tarjan(int x)
{
dfn[x] = low[x] = ++tot;
vis[x] = 1;
s[++top] = x;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(!dfn[to])
{
tarjan(to);
low[x] = min(low[x],low[to]);
}
else if(vis[to])
low[x] = min(low[x],dfn[to]);
}
if(dfn[x] == low[x])
{
++cnt;
while(s[top + 1] != x)
{
belong[s[top]] = cnt;
pts[cnt].push_back(s[top]);
vis[s[top]] = 0;
--top;
}
}
}
inline void dfs(int x)
{
vis[x] = 1;
for(int i = head[x];i;i = e[i].next)
{
int to = e[i].v;
if(belong[to] != belong[x]) continue;
if(!vis[to])
{
dep[to] = dep[x] + 1;
dfs(to);
}
else
d = __gcd(d,abs(dep[to] - dep[x] - 1));
}
}
int main()
{
cin>>n>>m>>k;
for(int i = 1,x,y;i <= m;i++)
{
cin>>x>>y;
add(x,y);
}
tot = 0;
for(int i = 1;i <= n;i++) if(!dfn[i]) tarjan(i);
memset(vis,0,sizeof(vis));
ll ans = 0;
for(int i = 1;i <= n;i++)
{
if(vis[i]) continue;
dep[i] = 1;
d = 0;
dfs(i);
if(!d) continue;
int max_dep = 0;
for(auto in : pts[belong[i]])
{
pot[dep[in]]++;
max_dep = max(max_dep,dep[in]);
}
if(k % d == 0)
{
for(int j = 1;j <= max_dep;j++)
{
ans += 1ll * pot[j] * (pot[j] + 1) / 2;
if(j > d)
{
ans += 1ll * pot[j] * pot[j - d];
pot[j] += pot[j - d];
}
}
}
else if((k % d) * 2 == d)
{
for(int j = d / 2 + 1;j <= max_dep;j++)
{
ans += 1ll * pot[j] * pot[j - d / 2];
pot[j] += pot[j - d];
}
}
fill(pot,pot + max_dep + 1,0);
}
cout<<ans;
return 0;
}
E
学习了洛谷上 Solystic 神犇的做法。
考虑当前策略只和几个信息有关:是否知道 \(backspace\) ,是否知道下一个敲的字母,以及当前敲的是否正确。
-
如果不知道 \(backspace\):
-
如果知道下一个敲的字母,直接敲即可。
-
如果不知道,就随便乱按,按错了就找出 \(backspace\) 然后删掉即可。
-
-
如果知道 \(backspace\) :
-
如果知道下一个敲的字母,直接敲。
-
如果不知道,就随便乱按,按错了就删掉。
-
我们发现只有遇到新字符是我们才可能乱按。
设 \(f_{i,j,1/2/3/4}\) 为已经输入了前 \(i\) 个字符,有 \(j\) 个字符知道键位,但是没有输入的期望步数。
1:不知道 \(backspace\) ,当前输入完全正确。
2:不知道 \(backspace\) ,当前输入不正确。
3:知道 \(backspace\) ,不确定是否知道下一个字符。
4:知道 \(backspace\) ,不知道下一个字符。
转移有 \(3 + 4 + 1 + 2 = 10\) 种转移,详见洛谷大佬博客。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5,M = 1005,MOD = 1e9 + 7;
int n,m,a[N],l[M],cnt = 0,vis[M];
typedef long long ll;
ll inv[N],f[M][M][5];
inline void add(ll &x,ll y){x = (x + y) % MOD;}
int main()
{
cin>>n>>m;
memset(l,0,sizeof(l));
for(int i = 1;i <= n;i++)
{
cin>>a[i];
if(!vis[a[i]]) vis[a[i]] = 1,l[cnt] = i - 1,++cnt;
}
l[cnt] = n;
memset(f,0,sizeof(f));
inv[1] = 1;
for(int i = 2;i <= M - 1;i++) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
for(int i = cnt - 1;i >= 0;i--)
for(int j = m - i;j >= 0;j--)
{
if(m - i - j > 0)
{
add(f[i][j][4],inv[m - i - j] * ((f[i + 1][j][3] + l[i + 1] - l[i]) % MOD) % MOD);
add(f[i][j][4],(m - i - j - 1) * inv[m - i - j] % MOD * (f[i][j + 1][4] + 2) % MOD);
}
if(j > 0) add(f[i][j][3],j * inv[m - i] % MOD * ((f[i + 1][j - 1][3] + l[i + 1] - l[i]) % MOD) % MOD);
add(f[i][j][3],(m - i - j) * inv[m - i] % MOD * f[i][j][4] % MOD);
add(f[i][j][2],(m - i - j) * inv[m - i - j + 1] % MOD * (f[i][j + 1][2] + 2) % MOD);
if(j > 0) add(f[i][j][2],inv[m - i - j + 1] * (j - 1) % MOD * inv[m - i - 1] % MOD * ((f[i + 1][j - 1][3] + l[i + 1] - l[i]) % MOD) % MOD);
add(f[i][j][2],inv[m - i - j + 1] * (m - i - j) % MOD * inv[m - i - 1] % MOD * f[i][j][4] % MOD);
if(j == 0)
{
add(f[i][0][1],inv[m - i + 1] * ((f[i + 1][0][1] + l[i + 1] - l[i]) % MOD) % MOD);
add(f[i][0][1],inv[m - i + 1] * (f[i][0][3] + 1 + (i != 0)) % MOD);
add(f[i][0][1],(m - i - 1) * inv[m - i + 1] % MOD * (f[i][1][2] + 2) % MOD);
}
}
cout<<f[0][0][1];
return 0;
}
F
咕。