题2
constructive algorithms
E. Modular Sequence **
关键在于如何在长度限制为 \(n\) 的情况下构造使 \(sum\) 正好等于 \(t\)。
\(d[i]\) 表示数和为 \(i\) 最小需要 \(d[i]\) 个数。建图 or dp转移。
然后枚举开头前一段有几个连续的,剩下的拼上去即可。
F. Two Different
长为 \(2^k\) 的序列,不管数是什么样的,都可以通过 \(k\times 2^k\) 次把它们变成一样的。
- 前缀弄一遍,后缀弄一遍。
B. Complete The Graph *
对所有待修改的边编号,按顺序给边的权值加一(循环着加),这样就可以保证最短路的值每次最多增加 \(1\)。
G. Inverse of Rows and Columns *
题目性质很好分析,主要难点在于如何用代码构造方案。
对于第一行和最后一行,至少有一行是完全一样的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[222][222],b[222][222],c[222];
bool check1(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]=a[i][j]^a[1][j];
}
}
int di=0;
for(int i=1;i<=n;i++){
int tmp=0;
for(int j=1;j<=m;j++) tmp+=b[i][j];
if(tmp!=0&&tmp!=m) di++;
c[i]=tmp;
}
if(di>1) return 0;
if(!di){
cout<<"YES"<<endl;
for(int i=1;i<=n;i++) cout<<(c[i]==m);
cout<<endl;
for(int i=1;i<=m;i++) cout<<a[1][i];
cout<<endl;
return 1;
}
int i,j;
for(i=1;i<=n;i++) if(c[i]!=0&&c[i]!=m) break;
for(j=2;j<=m;j++) if(b[i][j]!=b[i][j-1]) break;
for(int k=j+1;k<=m;k++)
if(b[i][k]!=b[i][k-1]) return 0;
cout<<"YES"<<endl;
for(int ii=1;ii<i;ii++) cout<<(c[ii]==m);
cout<<b[i][1];
for(int ii=i+1;ii<=n;ii++) cout<<(c[ii]==0);
cout<<endl;
for(int ii=1;ii<=m;ii++) cout<<a[1][ii];
cout<<endl;
return 1;
}
bool check2(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]=a[i][j]^a[n][j];
// cout<<b[i][j]<<" ";
}
// cout<<endl;
}
int di=0;
for(int i=1;i<=n;i++){
int tmp=0;
for(int j=1;j<=m;j++) tmp+=b[i][j];
if(tmp!=0&&tmp!=m) di++;
c[i]=tmp;
}
if(di>1) return 0;
if(!di){
cout<<"YES"<<endl;
for(int i=1;i<=n;i++) cout<<(c[i]==m);
cout<<endl;
for(int i=1;i<=m;i++) cout<<a[n][i];
cout<<endl;
return 1;
}
// for(int i=1;i<=n;i++) cout<<c[i]<<endl;
int i,j;
for(i=1;i<=n;i++) if(c[i]!=0&&c[i]!=m) break;
for(j=2;j<=m;j++) if(b[i][j]!=b[i][j-1]) break;
// cout<<i<<" "<<j<<endl;
for(int k=j+1;k<=m;k++)
if(b[i][k]!=b[i][k-1]) return 0;
cout<<"YES"<<endl;
for(int ii=1;ii<i;ii++) cout<<(c[ii]==0);
cout<<(!b[i][1]);
for(int ii=i+1;ii<=n;ii++) cout<<(c[ii]==m);
cout<<endl;
for(int ii=1;ii<=m;ii++) cout<<(!a[n][ii]);
cout<<endl;
return 1;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
if(!check1()){
if(!check2()){
cout<<"NO"<<endl;
}
}
return 0;
}
D. Dog Show
贪心。
考虑跑到第 \(i\) 停止,从而得到时间限制。
用优先队列存每碗食物需要的时间,把不合法的弹掉,有队列大小更新答案。
C. Little Pony and Summer Sun Celebration *
奇偶性可以通过走父节点进行再一次调整。
E. Square Root of Permutation ***
形成若干个环,模拟每次操作,发现奇环的大小不变,偶环每次大小减半。
题目要求找出上一步。依据上述性质判断是否有解。
然后构造。
G. Changing Brackets
能配对的括号位置的奇偶性一定不同。
H. K and Medians
最后一次选择的数一定是 \(b_i\) 中的,且必须满足比它大的和小的需要删的数都至少有 \(\frac{k-1}{2}\) 个。
考虑如何构造能使其两边只剩下\(\frac{k-1}{2}\) 个。仔细思考发现必然可以做到。
C. Between
假设存在限制 \((u,v)\),则 \(num[u]\le num[v]+1\)。以 \(1\) 为源点跑最短路算出每个数能出现的最大次数。然后按次数从大到小递减输出。
B. Little C Loves 3 II
从小棋盘开始,一个一个试一试。
考虑小棋盘哪些可以全填满,这些小棋盘可以拼成哪些大棋盘。
不能填满的小棋盘,答案是多少。
那些不能拼成的大棋盘有什么性质,或分离出一个什么样的棋盘可以变成小棋盘能拼成的大棋盘。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,ans;
int main(){
cin>>n>>m;
ans=n*m;
if(n>m) swap(n,m);
if(n==1) return cout<<(m/6*6+max(m%6-3,0ll)*2)<<endl,0;
else if(n==2){
if(m==2) cout<<0<<endl;
else if(m==3||m==7) cout<<ans-2<<endl;
else cout<<ans<<endl;
return 0;
}else{
cout<<ans-((m&1)&&(n&1))<<endl;
// if(m%2==0||n%2==0) cout<<ans<<endl;
// else cout<<ans-1<<endl;
}
return 0;
}
E. Construct the Binary Tree **
首先考虑两个问题:
- 节点个数位 \(n\) 的树,能构成的最大深度之和 \(L\) 和最小深度之和 \(R\) 分别是多少?
- \(L\) 到 \(R\) 内的取值是否连续?(思考一下发现答案是肯定的)
对于左右子树的分配情况,可以递归处理。
设想在两颗子树上加一个父节点(两颗子树的节点数总和为 \(num\)),那么新产生的贡献为 \(num\)(因为每个节点的深度都加了一)。
所以递归的时候把其减去,就变成了子问题。暴力枚举两颗子树的节点个数和深度之和,找到一组合法解即可。判断条件是子树对应的深度和在 \(L_{num}\) 到 \(R_{num}\) 内。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5100;
int d,n,cnt;
int mx[maxn],mn[maxn],tmp[maxn],f[maxn];
inline int lowbit(int x){return x&(-x);}
void ini(){
for(int i=1;i<=5010;i++) mx[i]=mx[i-1]+i-1;
for(int i=1;i<=5010;i++){
int x=i,cnt=0;
for(int j=1;j<=x;j*=2,cnt++){
mn[i]+=j*cnt;
x-=j;
}
mn[i]+=x*cnt;
}
// mn[0]=mx[0]=-1;
}
void dfs(int now,int num,int sum){
// if(!sum) return;
if(now>n) return;
if(now==n){
for(int i=1;i<=n;i++) f[i]=tmp[i];
return;
}
// cout<<now<<endl;
// for(int i=1;i<=cnt;i++) cout<<tmp[i]<<" ";cout<<endl;
num--;
if(!num) return;
int res=sum-num;
// if(!res) return;
for(int i=0;i*2<=num;i++){
if(mn[i]+mn[num-i]<=res&&res<=mx[i]+mx[num-i]){
for(int j=mn[i];j<=mx[i];j++){
int pj=res-j;
if(mn[num-i]<=pj&&pj<=mx[num-i]){
// cout<<"p "<<now<<" "<<tmp[now]<<" "<<i<<" "<<num-i<<" "<<j<<" "<<pj<<endl;
if(i){
tmp[++cnt]=now;
dfs(cnt,i,j);
}
tmp[++cnt]=now;
dfs(cnt,num-i,res-j);
break;
// cnt=o;
}
}
break;
}
}
}
int main(){
ini();
// for(int i=1;i<=10;i++) cout<<mn[i]<<" "<<mx[i]<<endl;
int T=0;cin>>T;
while(T--){
cin>>n>>d;
if(d<mn[n]||d>mx[n]){
cout<<"NO"<<endl;
continue;
}
cnt=1;
dfs(1,n,d);
cout<<"YES"<<endl;
for(int i=2;i<=n;i++) cout<<f[i]<<" ";cout<<endl;
}
return 0;
}
E. Cars *
给出条件的一条边连接的两个点,方向一定是相反的,所以可以进行黑白染色(2-sat/判断二分图)。
对于位置条件,有一定的大小关系,可以从位置小的向位置大的连边。若有环则无解,否则进行拓扑排序。
G1. In Search of Truth (Easy Version)
类根号分治。
先确定一段连续的值,然后按此长度跳,一定会再次跳回此区间。
F. K-th Path
第 \(k\) 大的距离,一定包含于前 \(k\) 小的边和它们构成的路径中。
D. Kill Anton
- 对当前字符串 \(s\) 进行操作,试证明使两个相同字符挨在一起一定是不劣的。
- 字母个数很少,枚举 \(4!\) 种排列求最大值。
B2. Koa and the Beach (Hard Version)
贪心+模拟
E. Equal Tree Sums **
递推
D. Three Sequences *
\(ans=\max(b_n,c_1)=max(b_1+\sum_{i=2}^{n}(b_i-b_{i-1}),c_1)=\lceil \frac{b_1+\sum_{i=2}^{n}(b_i-b_{i-1})+c_1}{2}\rceil\)。
假设 \(a_i\) 的值相等,此时答案为 \(\lceil\frac{a_i}{2}\rceil\),然后实际的 \(a_i\) 在此基础上进行加减。
设差为 \(d\)。
当差大于零,假设 \(c\) 贡献 \(k\),则 \(b\) 贡献 \(d-k\)。因为 \(c\) 单调不增,所以前面的数都要加 \(k\),则 \(b\) 前面的数都要减 \(k\),此时 \(b_{i'}-b_{i-1'}=b_i+d-k-(b_{i-1}-k)=b_i-b_{i-1}+d\)。所以对答案的贡献与怎么分配无关,为定值。
当差小于零,则全部由 \(c\) 贡献,这既不会影响 \(b_i\) 也不会影响 \(c_1\)。
E. Paired Payment
因为 \(w\) 的范围很小,所以 \(n^2\) 做法会有很多重复/不需要的东西。比如,当前以 \(u_1\) 为中间的点,与 \(u_2,u_3,u_4...\) 等若干个点,以相同的 \(w_1\) 连接,当 \(u_1\) 去更新 \(v\) 时,只有 \(w\) 是有用的,而从那个点来是无用的,所以可以舍去。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+100;
int n,m;
bool ins[maxn];
ll dis[maxn][55],inf=1e18;
struct P{
int to;
ll w;
};
vector<P> G[maxn];
priority_queue<pair<ll,pair<int,int> > > q;
void solve(){
q.push({0,{1,0}});
dis[1][0]=0;
while(q.size()){
int now=q.top().second.first;
int w2=q.top().second.second;
q.pop();
// cout<<now<<endl;
for(int i=0;i<G[now].size();i++){
P nx=G[now][i];
int to=nx.to;
ll w=nx.w;
if(w2){
if(dis[to][0]>dis[now][w2]+(w2+w)*(w2+w)){
dis[to][0]=dis[now][w2]+(w2+w)*(w2+w);
q.push({-dis[to][0],{to,0}});
}
}else{
if(dis[to][w]>dis[now][0]){
dis[to][w]=dis[now][0];
q.push({-dis[to][w],{to,w}});
}
}
// for(int j=0;j<G[to].size();j++){
// P nx2=G[to][j];
// int to2=nx2.to;
// if(to2==now) continue;
// if(dis[now]+(nx.w+nx2.w)*(nx.w+nx2.w)<dis[to2]){
// dis[to2]=dis[now]+(nx.w+nx2.w)*(nx.w+nx2.w);
// if(!ins[to2]) q.push({-dis[to2],to2});
// }
// }
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=0;j<=50;j++){
dis[i][j]=inf;
}
}
for(int i=1;i<=m;i++){
int u,v,w;
// cin>>u>>v>>w;
scanf("%d%d%d",&u,&v,&w);
G[u].push_back({v,w});
G[v].push_back({u,w});
}
solve();
for(int i=1;i<=n;i++) printf("%lld ",dis[i][0]==inf?-1:dis[i][0]);
puts("");
return 0;
}
E2. Salyg1n and Array (hard version)
-
the jury's program begins with reading two positive even integers n and k
-
只需保证每段询问奇数次,所有询问异或起来就是答案。
C. Perfect Triples
打表发现规律,确定第几组第几个后,四进制确定剩余的数值。
a: 00 01 10 11
b: 00 10 11 01
c: 00 11 01 10
按照顺序分别代表四进制下的 \(0,1,2,3\)。
E. Make It Increasing *
进行 \(a[i]-i\) 操作,把单调递增转化为单调不降。对于 \(6 \; 12 \; 9 \; 8 \; 10\) 这种情况处理起来也方便了。
D. Multiples and Power Differences *
对于 x 范围很小,且要求是其倍数的构造,可以求 lcm 。
D. Tree Requests
一颗子树的 dfs 序是连续的。
每次询问只对同一深度的节点进行查询,所以可以根据 dfs 序,确定这一深度有哪些点在以 v 为根的子树内。
因为对字母出现个数,只关注奇偶性,所以将记录前缀和变为异或操作。
E. Increasing Subsequences
为了不超过 200 的限制,可以让小的和大的序列交集尽可能的多。
D. Letter Picking
因为字母加到字符串的前面,所以越靠后选的,影响力越大。
杂
P2182 翻硬币
一开始设计的状态是 \(dp[i][k][j]\),表示前 \(i\) 个位置,转 \(k\) 次,有 \(j\) 个不同的方案数。
但是每次选择的 \(m\) 个硬币,可以任选,与位置无关,所以第一维可以舍掉。
P7735 [NOI2021] 轻重边
考虑“时间戳”。如果一条边是重边,那么其两端节点被操作的“时间”是相同的。
树剖+线段树。
关闭同步流不影响 endl
,要换成 \n
。
P7738 [NOI2021] 量子通信
考虑题目有哪些性质:
- 每个串的长度是一样且固定的。\(L=256\)
- \(k\) 的范围较小,只有 \(15\)。
- 只需判断是否存在,不需计数。
- 原串是随机的。
\(256=16 \times 16\),若把查询串 \(b\) 划分为长为 \(16\) 的 \(16\) 个串, 那么根据鸽巢原理,如果存在满足条件的原串 \(a\),那么至少有一个对应部分 \(a\) 和 \(b\) 是完全相同的。(1+2)
\(a\) 的子串与 \(b\) 完全相同的概率为 \(\frac{1}{65536}\)(\(2^{16}=65536\)),有 \(n\) 个串,那么期望为 \(\frac{n}{65536}\),不超过 \(7\)。即对于 \(b\) 串的第 \(i\) 部分,与它完全相同的原串 \(a\) 最多有 \(7\) 个。(4)
所以,可以按顺序枚举 \(b\) 串的第 \(i\) 部分(16),然后枚举与该部分完全相同的原串 \(a\)(7),计算其他部分 \(a\) 与 \(b\) 不相同的个数(16)。
则复杂度为 \(O(m \times 16 \times 7 \times 16 \approx 2 \times 10^8)\)。
The 2023 ICPC Asia Jinan Regional Contest
G. Gifts from Knowledge
建图,扩展域并查集
K. Rainbow Subarray
- 最优的相同的值是中位数。
- 数据结构
- 尺取法
ABC 337
E - Bad Juice
对每瓶饮料的编号进行二进制拆分,第 \(i\) 位为 1 就让第 \(i\) 个同学喝,最后把所有为 \(i\) 的或起来就算答案。要注意的是,如果 \(n=2^k\),第 \(k\) 个同学只喝一瓶,如果找 \(k-1\) 个同学,通过排除法,也可以判断第 \(2^k\) 瓶是否有问题。
F - Usual Color Ball Problems
ABC 338
D - Island Tour *
对于 \(u \rightarrow v\) \((u<v)\) 有两条路径 \(v-u\) 和 \(u+n-v\),距离差为 \(d=\left| 2v-2u-n \right|\)。那么当且仅当删去距离较小的路径上的一条边时才会对答案产生影响,即使答案加 \(d\)。那么可以求出删除一条边产生的贡献,取最小值。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e5+100;
int n,m;
int x[maxn];
ll ans,a[maxn];
int main(){
// freopen("data.in","r",stdin);
// freopen("result.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>x[i];
if(i==1) continue;
int u=min(x[i-1],x[i]);
int v=max(x[i-1],x[i]);
int d1=v-u,d2=u+n-v;
int r=abs(d1-d2);
if(d1<d2){
a[u]+=r;a[v]-=r;
}else{
a[1]+=r;a[u]-=r;
a[v]+=r;a[n+1]-=r;
}
ans+=min(d1,d2);
}
ll add=1e18;
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
add=min(add,a[i]);
}
cout<<ans+add<<endl;
return 0;
}
F - Negative Traveling Salesman
要注意的是,那些不存在的路径和不能到达的状态要跳过。
G - evall **
设 \(dp[i]\) 表示以 \(i\) 为左端点的 \(eval\) 和,\(s_{0,i}\) 表示第 \(i\) 位到 \(tp-1\) 位对答案的贡献,\(s_{1,i}\) 表示和一段连乘的总贡献,\(cnt[i]\) 表示 \(i\) 及以后出现的数字个数。设 \(ap\) 表示 \(i\) 后第一个出现 \(+\) 的位置,\(tp\) 表示 \(i\) 后第一个出现 \(*\) 的位置且 \(tp \le ap\)。则有:
\(s_{0,i}=str[i]\times \prod_{k=0}^{tp-i-1} 10^k +s_{0,i+1}\);
\(s_{1,i}=s_{0,i}+eval(i,tp-1) \times s_{1,tp+1}\);
\(dp[i]=s_{1,i}+eval(i,ap-1)\times cnt[ap+1] +dp[ap+1]\)。
ABC 342
E - Last Train
最后都到 n,所以反着建图,变成单源。答案求最大值,则跑最长路。
F - Black Jack **
庄家的决策是确定的,则可以从此入手。
设 \(g[i]\) 表示,最终 \(y=i\) 时庄家的胜率。
“我”的胜率由庄家输和 \(y<x<n\) 两部分组成。
设 \(f[i]\) 表示从 \(x=i\) 出发时“我”的胜率。有两种情况:停止 or 继续。
停止:\(f[i]=1-\sum_{k=1}^n g[k]+\sum_{k=1}^{i-1} g[i]\);
继续:\(f[i]=\frac{1}{d} \times \sum_{k=1}^d f[i+k]\)。
二者取 \(\max\) 。
\(f[0]\) 为答案。
G - Retroactive Range Chmax
ABC 341
D - Only one of two *
指定数列是由两部分组成:\(n\) 的倍数和 \(m\) 的倍数,同时舍弃 \(lcm(n,m)\) 的倍数。
假定 \(x\) 是数列中的一个数,它是第几个是可以确定的:第 $ \lfloor {\frac{x}{n}}\rfloor + \lfloor {\frac{x}{m}}\rfloor -2 \times \lfloor { \frac{x}{lcm(n,m)}}\rfloor$ 个。
那么找第 \(k\) 个就可以二分查找。
E - Alternating String
线段树维护区间左右两端的状态,并记录该区间是否合法。
F - Breakdown
假设 \(i\) 点的 \(a[i]\) 可以传递给 \(f[i]\) 个其他点,则最终答案为 \(\sum_{i=1}^n a[i] \times f[i]\)。
考虑如何求 \(f[i]\):
题目要求,在 \(w\) 之和不超过 \(w_x\) 的情况下,使 \(f[i]\) 最大,这就变成了一个背包问题,\(w\) 代表体积/容积,\(f[i]\) 为价值。
易知,\(w[i]\) 小的可以更新大的,所以按升序排列来做背包。
G - Highest Ratio **
题目可以转换为求 \(\max(\frac{s[r]-s[k]}{r-k})\)。(\(s[i]\) 代表前缀和)。
这形如 \(\frac{y1-y2}{x1-x2}\),即两点连线的斜率。所以问题就转换为求最大斜率。
倒着遍历,维护斜率单调上升的栈。(凸包)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e5+1000;
int n,a[maxn],q[maxn],t;
ll s[maxn];
double ans[maxn];
int main(){
// freopen("data.in","r",stdin);
// freopen("result.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
q[++t]=n;
for(int i=n-1;i>=0;i--){
while(t>1&&(s[q[t]]-s[i])*(q[t-1]-i)<=(s[q[t-1]]-s[i])*(q[t]-i)){
t--;
}
ans[i+1]=1.0*(s[q[t]]-s[i])/(q[t]-i);
q[++t]=i;
}
for(int i=1;i<=n;i++) printf("%.10lf\n",ans[i]);
return 0;
}
ABC 340
C - Divide and Divide
记忆化搜索。
D - Super Takahashi Bros
题目相当于 \(i\) 分别向 \(i+1\) 和 \(X_i\) 建权值分别为 \(A_i\) 和 \(B_i\) 的边,求 \(1\) 到 \(n\) 的最段路。
E - Mancala 2
VP 的时候写的差分+树状数组,题解好像是直接线段树。
F - S = 1
画个图,把求三角形面积转化为一个大三角形减去一个小三角形和一个梯形的面积。然后化简式子,就得到了:\(AY-BX=2\)。\(exgcd\) 求解即可,输出时 \(B\) 要取负号。
G - Leaf Color **
先考虑如果指定一种颜色要怎么做:
设 \(dp[u]\) 表示,以 \(u\) 为根的子树内,除了 \(u\) 以外叶子颜色均为 \(c\) 且包含 \(u\) 的连通块方案数。那么有 :\(dp[u]=\prod (dp[son]+1)-1\)(减一是减去全不选的方案数)。当 \(color[u]=c\) 时,\(dp[u]\) 直接贡献到答案,反之,要减去 \(u\) 作为叶子的方案数,即减去 \(1+\sum dp[son]\),同时 \(dp[u]--\)。这时复杂度 \(O(n^2)\)
我们发现,有很多颜色不为 \(c\) 的结点,对答案其实无贡献,所以可以删去。这时引入虚树。
这样就可以降复杂度。