Preface
久违地VP一场,虽然打的挺唐但勉强写出了8题
前中期EFB三开三卡属实有点难受,而且B题这个套路大合集我和徐神两个人写了快200行也占用了一定机时
但好在后面把卡住的题慢慢都写出来了,然后最后40min冲刺L题成功
比较可惜的是I这个开场看的题没有再细想一步,感觉想到在线段树上DP就很容易做了
A. Sequence and Sequence
防AK题,开场看了眼题面一眼不可做直接扔了
B. Kawa Exam
超级经典套路题,徐神1h就开了这道题然后开始写,后面我接力上去写了个DSU on Tree加线段树,这就是我们热血沸腾的组合技啊
首先原问题等价于,每个点有一个颜色,每个连通块的权值定义为其中出现次数最多的颜色的值
图的权值定义为所有连通块的权值和,现在要对于每一条边求出,断掉这条边后图的权值是多少
看到断边和连通性,很容易想到求出桥,显然不是桥的那些边删不删对答案没有影响,可以预先计算出
然后根据经典结论,将一个无向图边双缩点后,得到的一定是个森林,因此只要考虑在树上断一条边求解原问题
树上的断边又等价于选出一个子树,求出它的贡献以及删去整个子树后剩下部分的贡献
直接DSU on Tree扫描子树,同时线段树分别维护子树内和子树外每种颜色出现的次数即可,总复杂度\(O(n\log^2 n)\)
#include <bits/stdc++.h>
constexpr int $n = 200005;
std::vector<std::pair<int, int>> out[$n];
int dfn[$n], low[$n], tarjan_O;
std::vector<std::pair<int, int>> edge;
std::vector<bool> is_bridge;
int fa[$n];
bool vis[$n];
inline int father(int i) {
if(fa[i] == i) return i;
return fa[i] = father(fa[i]);
}
void tarjan(int u, int pre) {
low[u] = dfn[u] = ++tarjan_O;
vis[u] = true;
int son = 0;
int pre_cnt = 0;
for(auto [v, id]: out[u]) {
if(v == pre && pre_cnt == 0) { pre_cnt += 1; continue; }
if(!dfn[v]) {
son += 1;
tarjan(v, u);
if(low[u] > low[v]) low[u] = low[v];
if(low[v] > dfn[u]) is_bridge[id] = true;
} else if(low[u] > dfn[v]) {
low[u] = dfn[v];
}
}
}
int n, m,C;
int a[$n], ans[$n], baseline;
std::vector<int> hkr[$n];
int ans_partial[$n];
std::map<int, int> rst[$n];
std::map<int, int> dfs1(int cur, int pre) {
std::map<int, int> res {};
vis[cur] = true;
for(auto hkr: hkr[cur]) res[hkr] += 1;
for(auto [ch, id]: out[cur]) if(ch != pre) {
auto s = dfs1(ch, cur);
if(s.size() > res.size()) std::swap(s, res);
for(auto [k, v]: s) res[k] += v;
}
return res;
}
#define CI const int&
class Segment_Tree
{
private:
int mx[$n<<2];
public:
#define TN CI now=1,CI l=1,CI r=C
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void updata(CI pos,CI mv,TN)
{
if (l==r) return (void)(mx[now]+=mv); int mid=l+r>>1;
if (pos<=mid) updata(pos,mv,LS); else updata(pos,mv,RS);
mx[now]=std::max(mx[now<<1],mx[now<<1|1]);
}
inline int query(void)
{
return mx[1];
}
#undef TN
#undef LS
#undef RS
}IN,OUT;
int son[$n],sz[$n],bid[$n];
inline void getson(CI now,CI fa=0)
{
sz[now]=1; son[now]=0; vis[now]=true;
for (auto [to,_]:out[now]) if (to!=fa)
{
getson(to,now); sz[now]+=sz[to];
if (sz[to]>sz[son[now]]) son[now]=to,bid[now]=_;
}
}
inline void travel(CI now,CI fa,CI tp)
{
for (auto x:hkr[now]) IN.updata(x,tp),OUT.updata(x,-tp);
for (auto [to,_]:out[now]) if (to!=fa) travel(to,now,tp);
}
inline void DSU(CI outer,CI now,CI fa=0,CI id=-1,CI flag=0)
{
for (auto [to,_]:out[now]) if (to!=fa&&to!=son[now]) DSU(outer,to,now,_,0);
if (son[now]) DSU(outer,son[now],now,bid[now],1);
for (auto [to,_]:out[now]) if (to!=fa&&to!=son[now]) travel(to,now,1);
for (auto x:hkr[now]) IN.updata(x,1),OUT.updata(x,-1);
if (~id) ans[id]=outer+IN.query()+OUT.query();
if (!flag) travel(now,fa,-1);
}
void solve(CI outer,CI root) {
for (auto [x,y]:rst[root]) OUT.updata(x,y);
getson(root); DSU(outer,root);
for (auto [x,y]:rst[root]) OUT.updata(x,-y);
}
#undef CI
void work() {
std::cin >> n >> m; C=0; tarjan_O = 0;
for(int i = 1; i <= n; ++i) low[i] = dfn[i] = 0;
for(int i = 1; i <= n; ++i) std::cin >> a[i],C=std::max(C,a[i]);
for(int i = 1; i <= n; ++i) out[i].clear();
edge.resize(m), is_bridge.assign(m, false);
for(int i = 0; i < m; ++i) {
auto &[f, t] = edge[i];
std::cin >> f >> t;
out[f].emplace_back(t, i), out[t].emplace_back(f, i);
}
for(int i = 1; i <= n; ++i) vis[i] = false;
for(int i = 1; i <= n; ++i) if(!vis[i]) tarjan(i, i);
// for(int i = 0; i < m; ++i) if(is_bridge[i]) std::cerr << edge[i].first << " <-> " << edge[i].second << char(10);
// std::cerr << "-------\n";
// return ;
for(int i = 1; i <= n; ++i) fa[i] = i;
for(int i = 0; i < m; ++i) if(!is_bridge[i]) fa[father(edge[i].first)] = father(edge[i].second);
for(int i = 1; i <= n; ++i) out[i].clear();
std::set<std::pair<int, int>> edge_fuck;
for(int i = 0; i < m; ++i) if(is_bridge[i]) {
auto [_f, _t] = edge[i];
int f = father(_f), t = father(_t);
if(f > t) std::swap(f, t);
if(edge_fuck.find({f, t}) != edge_fuck.end()) continue;
edge_fuck.insert({f, t});
out[f].emplace_back(t, i), out[t].emplace_back(f, i);
}
for(int i = 1; i <= n; ++i) hkr[i].clear();
for(int i = 1; i <= n; ++i) hkr[father(i)].push_back(a[i]);
baseline = 0;
for(int i = 1; i <= n; ++i) vis[i] = false;
for(int i = 1; i <= n; ++i) if(fa[i] == i && !vis[i]) {
rst[i] = dfs1(i, i);
ans_partial[i] = 0;
// std::cerr << "i = " << i << char(10);
// for(auto [k, v]: rst[i]) std::cerr << "k, v = " << k << ", " << v << char(10);
for(auto [k, v]: rst[i]) if(v > ans_partial[i]) ans_partial[i] = v;
baseline += ans_partial[i];
// std::cerr << "ans_partial[" << i << "] = " << ans_partial[i] << char(10);
} else rst[i].clear();
// std::cerr << baseline << char(10);
// std::cerr << "-------\n";
// return ;
for(int i = 1; i <= n; ++i) vis[i] = false;
for(int i = 1; i <= n; ++i) if(fa[i] == i && !vis[i]) solve(baseline-ans_partial[i],i);
for(int i = 0; i < m; ++i) is_bridge[i]
? std::cout << ans[i] << char(i == m - 1 ? 10 : 32)
: std::cout << baseline << char(i == m - 1 ? 10 : 32);
return ;
}
int main() {
// freopen("B.in","r",stdin);
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) work();
return 0;
}
C. Flippy Sequence
分类讨论题,找出两个串不同的极长区间的个数,记为\(cnt\),则:
- \(cnt>2\),显然无解
- \(cnt=2\),手玩以下会发现一定恰有\(6\)种方法,而且样例给出了这种情况所以很容易理解
- \(cnt=1\),刚开始被坑了以为和区间长度有关,后面仔细想了下发现就是\(2\times (n-1)\)
- \(cnt=0\),两次选的区间相同,方案数为\(\frac{n(n+1)}{2}\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n; char a[N],b[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i; scanf("%d%s%s",&n,a+1,b+1);
int cnt=0,lst=0;
for (i=1;i<=n;++i) if (a[i]==b[i])
{
if (lst+1<=i-1) ++cnt; lst=i;
}
if (lst+1<=n) ++cnt;
if (cnt>2) { puts("0"); continue; }
if (cnt==2) { puts("6"); continue; }
if (cnt==1) printf("%d\n",2*(n-1));
else printf("%lld\n",1LL*n*(n+1)/2LL);
}
return 0;
}
D. Magic Multiplication
神秘模拟题,细节还是挺多的但想清楚还是很简单的
首先由于没有前导零,因此可以从\(1\sim 9\)枚举\(a_1\)的取值,当\(a_1\)确定时我们可以唯一确定出\(b_1\sim b_m\)的值
注意如果某个一位数\(x\)是\(a_1\)的倍数,那么对应的\(b_i\)必须是\(\frac{x}{a_1}\),否则如果在后面再加一个数得到的\(b_i\)一定是两位数
否则考虑取出两位来作为\(a_1\)和\(b_i\)的乘积,但要注意判掉前导零和得到的\(b_i>9\)的情况
现在我们已经得到了一组\(b_1\sim b_m\),然后就是用这些值来反着确定\(a_2\sim a_n\)的值了
注意当\(b_i=0\)时对应的\(a_i\)可以任意取值,因此有一些细节需要注意,最后如果回代检验成功就直接输出
具体细节可以看代码
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,a[N],b[N]; char s[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j; scanf("%d%d%s",&n,&m,s+1);
int len=strlen(s+1); bool flag=0;
for (a[1]=1;a[1]<=9&&!flag;++a[1])
{
bool sign=1; int p=1;
for (i=1;i<=m;++i)
{
if (p>len) { sign=0; break; }
if ((s[p]-'0')%a[1]==0)
{
b[i]=(s[p]-'0')/a[1];
++p; continue;
}
if (s[p]=='0') { sign=0; break; }
if (p+1>len) { sign=0; break; }
if (((s[p]-'0')*10+(s[p+1]-'0'))%a[1]==0)
{
b[i]=((s[p]-'0')*10+(s[p+1]-'0'))/a[1];
if (b[i]>9) { sign=0; break; }
p+=2; continue;
}
sign=0; break;
}
for (i=2;i<=n&&sign;++i)
{
a[i]=-1; for (j=1;j<=m;++j)
{
if (b[j]!=0)
{
if (p>len) { sign=0; break; }
if ((s[p]-'0')%b[j]==0)
{
int tmp=(s[p]-'0')/b[j];
if (tmp>9) { sign=0; break; }
if (a[i]==-1) a[i]=tmp;
else if (a[i]!=tmp) { sign=0; break; }
++p; continue;
}
if (s[p]=='0') { sign=0; break; }
if (p+1>len) { sign=0; break; }
if (((s[p]-'0')*10+(s[p+1]-'0'))%b[j]==0)
{
int tmp=((s[p]-'0')*10+(s[p+1]-'0'))/b[j];
if (tmp>9) { sign=0; break; }
if (a[i]==-1) a[i]=tmp;
else if (a[i]!=tmp) { sign=0; break; }
p+=2; continue;
}
} else
{
if (p>len) { sign=0; break; }
if (s[p]!='0') { sign=0; break; }
++p; continue;
}
}
if (a[i]==-1) a[i]=0;
}
if (sign&&p==len+1)
{
for (i=1;i<=n;++i) putchar(a[i]+'0'); putchar(' ');
for (i=1;i<=m;++i) putchar(b[i]+'0'); putchar('\n');
flag=1;
}
}
if (!flag) puts("Impossible");
}
return 0;
}
E. Plants vs. Zombies
从开局搞到最后发现原来是爆long long
了,真是经经又典典啊
首先最小值最大一眼二分答案,然后我们此时可以轻松确定出每个位置需要被走到的最少次数
考虑从前往后贪心处理,我们钦定处理到位置\(i\)时所有\(<i\)的位置已经满足要求了,那么要刷步数的话就直接在\((i,i+1)\)刷是最优的
总复杂度\(O(n\log Ans)\),注意潜在的爆long long
问题
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int INF = (int)4e18+5;
const int N = 1e5+5;
int t, n, m, A[N], c[N];
bool check(int val){
// printf("check(%lld)\n", val);
for (int i=1; i<=n; ++i){
c[i]=(val+A[i]-1)/A[i];
}
__int128 res=0;
for (int i=1; i<=n; ++i){
if (i<n || (n==i && c[i]>0)) ++res, --c[i];
if (c[i]>0) c[i+1]-=c[i], res+=2*c[i], c[i]=0;
}
// printf("res=%lld\n", res);
return res<=m;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
cin >> n >> m;
for (int i=1; i<=n; ++i) cin >> A[i];
int L=0, R=INF;
while (L<R){
int M = L+(R-L)/2+1;
if (check(M)) L=M;
else R=M-1;
}
cout << L << '\n';
}
return 0;
}
F. Tournament
神秘题,刚开始想漏了一个关键条件卡了我和徐神好久的时间,后面徐神上去写B我在下面冷静后发现好像是个找规律题
大力手玩会发现,当\(n\)为奇数时显然无解,\(2\mid n\and 4\nmid n\)时\(k\)至多为\(1\),这些都是很trivial的
然后画一画会发现\(n=8\)时可以达到\(n-1\)轮的上界,而\(n=12\)时最多就只能做\(3\)轮
索性大力猜测对于数\(n\)它能做到的最多轮次就是\(\operatorname{lowbit}(n)-1\),然后构造方案就直接异或就行
爬上去5min写了一发发现直接过了,我直接呃呃
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,k;
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j; scanf("%d%d",&n,&k);
auto lowbit=[&](CI x)
{
return x&-x;
};
if (k>lowbit(n)-1) { puts("Impossible"); continue; }
for (i=1;i<=k;++i) for (j=0;j<n;++j) printf("%d%c",(j^i)+1," \n"[j==n-1]);
}
return 0;
}
G. Repair the Artwork
看过题人数还是挺可做的一个题,但赛时机时不够了只能作罢,之后有空再补吧
H. Mirror
没一个人过的几何,很符合我对几何的想象
I. Soldier Game
开场就看了这个题,然后一直摸到结束也不会,后面看了眼题解发现就是个傻逼题
首先这类极差问题的经典思路就是枚举确定最小值,然后在只考虑大于阈值的区间时能得到的最大值
这题虽然乍一看数据范围挺大,但仔细一想会发现合法的区间最多只有\(2n-1\)个,因此可以先排序然后枚举
现在问题就是怎么快速处理在ban掉部分区间的情况下用剩余区间覆盖\(1\sim n\),且最大权值最小
不妨考虑用线段树优化DP,在线段树\(x\)(对应区间\([l,r]\))的节点上维护\(f_{x,0/1,0/1}\),表示当覆盖区间\([l,r]\),且覆盖\(l\)位置的线段在/不在区间外,覆盖\(r\)位置的线段在/不在区间外的情形下,最小化的最大权值
转移的话也很trivial:
\[f_{x,p,q}=\min_{k\in \{0,1\}} \max(f_{ls(x),p,k},f_{rs(x),k,q}) \]初始值的设置按照定义推一下也不难,然后每次删除一条线段就直接把线段树上对应的权值改成\(\infty\)即可
总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<array>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e18;
int t,n,a[N];
class Segment_Tree
{
private:
int f[N<<2][2][2];
inline void pushup(CI now)
{
for (RI x=0;x<2;++x) for (RI y=0;y<2;++y)
{
f[now][x][y]=INF; for (RI k=0;k<2;++k)
f[now][x][y]=min(f[now][x][y],max(f[now<<1][x][k],f[now<<1|1][k][y]));
}
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void build(TN)
{
if (l==r)
{
f[now][0][0]=a[l]; f[now][0][1]=l<n?a[l]+a[l+1]:INF;
f[now][1][0]=l>1?-INF:INF; f[now][1][1]=INF; return;
}
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline void modify(CI pos,CI len,TN)
{
if (l==r)
{
if (len==1) f[now][0][0]=INF; else f[now][0][1]=INF; return;
}
int mid=l+r>>1; if (pos<=mid) modify(pos,len,LS); else modify(pos,len,RS); pushup(now);
}
inline int query(void)
{
return f[1][0][0];
}
#undef TN
#undef LS
#undef RS
}SEG;
signed main()
{
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
vector <array <int,3>> vec; SEG.build();
for (i=1;i<=n;++i) vec.push_back({a[i],i,1});
for (i=1;i<n;++i) vec.push_back({a[i]+a[i+1],i,2});
sort(vec.begin(),vec.end()); int ans=INF;
for (auto [val,p,len]:vec)
ans=min(ans,SEG.query()-val),SEG.modify(p,len);
printf("%lld\n",ans);
}
return 0;
}
J. Books
徐神开场写的,我题面都没看
#include <bits/stdc++.h>
using llsi = long long signed int;
void work() {
int n, m;
std::cin >> n >> m;
std::vector<llsi> a(n);
for(int i = 0; i < n; ++i) std::cin >> a[i];
llsi ans = 0;
int c0 = 0;
for(int i = 0; i < n; ++i) c0 += !a[i];
if(c0 > m) return void(std::cout << "Impossible\n");
m -= c0;
llsi sufmin = 1e17;
for(int i = 0; i < n; ++i) if(a[i]) {
if(m > 0) {
ans += a[i];
m -= 1;
} else if(m == 0) {
sufmin = std::min(sufmin, a[i] - 1);
}
}
ans += sufmin;
if(ans >= 1e17)
std::cout << "Richman\n";
else
std::cout << ans << char(10);
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T;
while(T--) work();
return 0;
}
K. Airdrop
好像也是个可做题,但经典没时间了直接弃疗
L. Sub-cycle Graph
小清新计数题,稍微想一下就会了
首先先特判各种Corner Case,比如\(m>n\)时无解;\(m=n\)时为\(\frac{(n-1)!}{2}\)(不计方向的圆排列,所以要除以\(2\));\(m=0\)时为\(1\)
然后考虑一般的情况,不妨设度数为\(i\)的点的个数为\(d_i\),显然我们枚举\(d_1\)的值后就可以唯一确定\(d_0,d_2\)的值了
考虑现在图的形态,我们首先把编号划分给三种点,方案数为\(C_n^{d_0}\times C_{n-d_0}^{d_1}\),然后忽略掉所有的\(d_0\)
考虑先把\(d_1\)两两配对作为一条链的两个端点,这个的方案数是个经典问题
式子是\(C_{d_1}^{\frac{d_1}{2}}\times (\frac{d_1}{2})!\times \frac{1}{2^{\frac{d_1}{2}}}\),第一个组合数表示先定下一堆点放左边,然后剩下的点放在右边配对有阶乘种放法,但要注意因为后面加入的度数为\(2\)的点在中间的时候已经消除了顺序的影响,因此两端点顺序无关,每个pair
都要除以\(2\)
最后插入度数为\(2\)的点可以这样考虑,先把\(d_2\)的点任意排列,然后再划分成\(\frac{d_1}{2}\)组,每组内元素连续
后者是个经典的插板法例题,因此再乘上\(d_2!\times C_{d_2+\frac{d_1}{2}-1}^{\frac{d_1}{2}}\)即可
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr llsi mod = 1'000'000'007;
constexpr llsi ksm(llsi a, llsi b) {
llsi res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
constexpr llsi inv2 = ksm(2, mod - 2);
llsi fac[1000006], facinv[1000006] , inv_pw2[1000006];
void prep_fac(llsi n = 1000000) {
fac[0] = 1;
for(llsi i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
facinv[n] = ksm(fac[n], mod - 2);
for(llsi i = n; i >= 0; --i) facinv[i - 1] = facinv[i] * i % mod;
assert(facinv[0] == 1);
inv_pw2[0] = 1; inv_pw2[1] = inv2;
for(llsi i = 2; i <= n; ++i) inv_pw2[i] = inv_pw2[i - 1] * inv2 % mod;
return ;
}
inline llsi C(llsi n, llsi m) {
if(n < 0 || m < 0 || m > n) return 0;
return fac[n] * facinv[m] % mod * facinv[n - m] % mod;
}
llsi work() {
llsi n, m; std::cin >> n >> m;
if(m > n) return 0;
if(m == n) {
if(n <= 2) return 0;
return fac[n - 1] * inv2 % mod;
}
if(m == 0) return 1;
llsi ans = 0;
for(llsi d1 = 2; d1 <= n; d1 += 2) {
const llsi d2 = (2 * m - d1) / 2;
const llsi d0 = n - d1 - d2;
if(d2 < 0 || d2 > n || d0 < 0 || d0 > n) continue;
ans +=
C(n, d0) * C(n - d0, d1) % mod * (
C(d1, d1 / 2) * fac[d1 / 2] % mod
* inv_pw2[d1 / 2] % mod
* fac[d2] % mod
* C(d2 + d1 / 2 - 1, d1 / 2 - 1) % mod) % mod;
}
return ans % mod;
}
int main() {
std::ios::sync_with_stdio(false);
prep_fac();
int T; std::cin >> T; while(T--) std::cout << work() << char(10);
return 0;
}
M. Function and Function
祁神开场写的签,我题都没看
#include<bits/stdc++.h>
using namespace std;
int t, x, k;
const int tbl[10] = {1, 0, 0, 0, 1, 0, 1, 0, 2, 1};
int calc(int v){
int res=0;
while (v>0){
res += tbl[v%10];
v/=10;
}
return res;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
cin >> x >> k;
while (k>0 && x>0){
--k;
x = calc(x);
}
if (k>0) cout << k%2 << '\n';
else cout << x << '\n';
}
return 0;
}
Postscript
晚上要上人工智能的实验课,而且明天要补五一的课,晚上有CF都懒得打
唉感觉最近训练量堪忧啊,好久没有打CF/ATC了的说
标签:std,CI,include,Contest,int,res,Universal,now,Qingdao From: https://www.cnblogs.com/cjjsb/p/18162267