CF1635C Differential Sorting
link,R1200
题意
给定长度为n的序列a。对序列进行操作,每次操作可以选定一组x,y,z满足1≤x<y<z≤n,将ax替换为ay−az。
通过不超过n次操作将序列变为单调不减的,给出方案,或明确方案不存在。
n≤2⋅105
思路
显然若an−1>an则一定不存在方案,因为我们无法替换这两个元素。
根据操作对序列元素的影响,考虑以从后往前的顺序处理问题。
容易发现若az≥0,我们可以使ax≤ay;进一步的,如果存在ai≥0,我们一定可以使a1…ai−2单调不减。
于是只需考察an是否为非负。
若an≥0,则只需对a1…an−2从从后往前依次执行一次<x,y,z>=<i,i+1,n>的操作即可。
否则,检查原始序列是否单调不减,是则不需要对序列进行操作,否则方案不存在。
假设an<0,ax+1…an单调不减,而ax>ax+1。若要修改ax,我们能够构造出的最小的ay−az=ax+1−az>ax+1;若考虑修改ax+1,则会使其大于ax+2
复杂度O(n)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <iostream> #include <cmath> #include <cstdlib> #include <algorithm> #include <cstring> #include <cstdio> using namespace std; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
const int maxn=200000+100; int T; int n; ll a[maxn]; int X[maxn],Y[maxn],Z[maxn];
void Work() { read(n); for(int i=1;i<=n;++i) { read(a[i]); } if(a[n-1]>a[n]) { puts("-1"); return; } if(a[n]<0) { for(int i=1;i<n;++i) { if(a[i]>a[i+1]) { puts("-1"); return; } } puts("0"); return; } else { printf("%d\n",n-2); for(int i=n-2;i;--i) { printf("%d %d %d\n",i,i+1,n); } }
}
int main() { read(T); while(T--) { Work(); } return 0; }
|
CF1646C Factorials and Powers of Two
link,R1500
题意
将一个数n拆解为k个两两不同的2的幂或阶乘之和,最小化k或明确这样的k不存在
n≤1012
思路
一个数显然可以表示成若干个不同的2的幂之和,所以分解方案是一定存在的。
注意到阶乘d!随d的增长极快(事实上,14!<1012<15!),我们只需要枚举有哪些阶乘出现在了n的分解结果中,然后计算剩余部分的二进制表示中有多少个1即可
复杂度O(214⋅logn)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include <bits/stdc++.h> using namespace std; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
ll fac[15]; int T; ll n; int ans;
void pre() { fac[0]=1; for(ll i=1;i<=14;++i) fac[i]=fac[i-1]*i; }
int Count(ll x) { int re=0; while(x) { re+=(x&1); x>>=1; } return re; }
void Search(int step,int use,ll rest) { if(step==15) { ans=min(ans,use+Count(rest)); return; } Search(step+1,use,rest); if(rest>=fac[step]) Search(step+1,use+1,rest-fac[step]); }
void Work() { read(n); ans=0x7fffffff; Search(0,0,n); printf("%d\n",ans); }
int main() { pre(); read(T); while(T--) { Work(); } return 0; }
|
CF1646D Weight the Tree
link,R2000
第一次在赛场上做出D祭上大分咯
题意
给定n个节点的无根树。如果树中某节点的权值为与其相邻的节点的权值之和,我们称这个节点是“好”的
找到一种节点权值分配方案,在最大化好节点的个数的前提下最小化节点权值之和,输出该个数、和以及分配方案。
n≤2⋅105
思路
注意到当树节点数量为2时,两个节点都可以是好的。特判这种情况,输出
即可
当树的节点数量大于2时,显然任意两个好节点都不可能相邻;又注意到若一个节点不是好的,那么其权值可以为任意值,令其为1可使权值和尽可能小。
考虑树形dp,显然dp顺序对答案没有影响,定义状态dpu,0/1表示节点u不是/是好节点的情况下,以节点u为根的子树中好节点的最大数量,转移方程:
dpu,0=∑v∈son(u)max{dpv,0,dpv,1}
dpu,1=∑v∈son(u)dpv,0+1
同时,考虑到我们还需要最小化权值和,维护sumu,0/1表示节点u不是/是好节点的情况下,以节点u为根的子树的权值和
转移方程:
sumu,0=∑v∈son(u)sumv,∗+1(∗取决于dpv,0和dpv,1中谁对dpu,0有贡献)
sumu,1=∑v∈son(u)sumv,0+∑(u,v)∈G1
为方便分配权值,使用n个set
维护若节点u不好,它的哪些儿子是好的
考察u不好的情况时,若dpv,0=dpv,1,那么sumu,0=min{sumv,0,sumv,1};若仍相同,则认为此时v不是好的
Tutorial好像说这样一定能使sum最小化,但我不会证
答案即为max{dp1,0,dp1,1},二者相同时采取与上述过程相同的分配方案。
复杂度O(nlogn)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| #include <bits/stdc++.h> using namespace std; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
const int maxn=200000+100; int n; int dp[maxn][2]; ll sum[maxn][2]; ll ass[maxn]; set<int> goodson[maxn];
struct edge { int u,v,nxt; }g[maxn*2];
int head[maxn],ecnt; void eADD(int u,int v) { g[++ecnt].u=u;g[ecnt].v=v;g[ecnt].nxt=head[u];head[u]=ecnt; }
void DFS(int u,int prt) { for(int i=head[u];i;i=g[i].nxt) { int v=g[i].v; if(v==prt) continue; DFS(v,u); dp[u][0]+=max(dp[v][0],dp[v][1]); if(dp[v][1]>dp[v][0]) { goodson[u].insert(v); sum[u][0]+=sum[v][1]; } else if(dp[v][1]<dp[v][0]) sum[u][0]+=sum[v][0]; else { if(sum[v][0]>sum[v][1]) { goodson[u].insert(v); sum[u][0]+=sum[v][1]; } else sum[u][0]+=sum[v][0]; }
dp[u][1]+=dp[v][0]; sum[u][1]+=sum[v][0]+1; } ++dp[u][1]; if(prt) ++sum[u][1]; ++sum[u][0]; }
void Assign(int u,int isgood,int prt) { if(isgood) { for(int i=head[u];i;i=g[i].nxt) { ass[u]++; } for(int i=head[u];i;i=g[i].nxt) { int v=g[i].v; if(v==prt) continue; Assign(v,0,u); } } else { ass[u]=1; for(int i=head[u];i;i=g[i].nxt) { int v=g[i].v; if(v==prt) continue; if(goodson[u].find(v)!=goodson[u].end()) Assign(v,1,u); else Assign(v,0,u); } } }
int main() { read(n); for(int i=1;i<n;++i) { int u,v; read(u),read(v); eADD(u,v),eADD(v,u); } if(n==2) { puts("2 2"); puts("1 1"); return 0; } DFS(1,0); int root; if(dp[1][0]==dp[1][1]) root=(sum[1][0]>sum[1][1]); else root=(dp[1][0]<dp[1][1]); printf("%d %lld\n",dp[1][root],sum[1][root]); Assign(1,root,0); for(int i=1;i<=n;++i) printf("%lld ",ass[i]); return 0; }
|
其实这场我感觉还蛮套路的,但是莫名其妙就上分了
D花的时间有点多(主要是考虑怎么做方案分配的部分),不然说不定还能搞一搞E
CF1651C Fault-tolerant Network
link
题意
给定两条长度均为n的链,点带权,第一条链上第i个点权为ai,第二条链上第i个点权为bi。连接两条边链上的两个点ai,bj的代价为∣ai−bj∣,问使得整个图成为点双的最小代价。
原题意为:使得图在去掉任意一个点的情况下仍然连通,该表述思路更自然
n≤2⋅105
思路
考虑去掉的是一条链中间的某个点,要让这个点左边的点仍能到达右边的点,必须在其左边和右边均存在一个点连接到了另一条边。显然若完成连接后去掉任意一个点仍能使整个图连通,两条链的端点必须连接到另一条链上。
枚举四个端点连到另一条边的最小代价,计算答案,发现可能存在端点之间相互连接的情况。讨论各种端点相连的情况,与答案进行比较取最小值即为最终答案。
这些情况分别包括:所有端点均与端点相连(2种),仅1个端点未与端点相连(4种),2个端点未与端点相连(4种)。
复杂度O(n)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <bits/stdc++.h> using namespace std; typedef pair<int,int> pii; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
const int maxn=200000+100; const int dx[]={-1,0,0,1},dy[]={0,-1,1,0}; int T; int n; pii p[maxn]; pii ans[maxn]; int vst[maxn]; map<pii,int> idx;
int dis(pii a,pii b) { return abs(a.first-b.first)+abs(a.second-b.second); }
void Work() { read(n); for(int i=1;i<=n;++i) { read(p[i].first),read(p[i].second); idx.insert({p[i],i}); } queue<int> q; for(int i=1;i<=n;++i) { for(int j=0;j<4;++j) { int X=p[i].first+dx[j],Y=p[i].second+dy[j]; if(idx.find({X,Y})==idx.end()) { ans[i]={X,Y}; q.push(i); break; } } } while(!q.empty()) { int uidx=q.front(); pii u=p[uidx]; q.pop(); for(int i=0;i<4;i++) { int X=u.first+dx[i],Y=u.second+dy[i]; if(idx.find({X,Y})!=idx.end()) { int vidx=idx[{X,Y}]; pii v=p[vidx]; if(dis(ans[uidx],v)<dis(ans[vidx],v)) { ans[vidx]=ans[uidx]; q.push(vidx); } } } } for(int i=1;i<=n;++i) printf("%d %d\n",ans[i].first,ans[i].second); }
int main() {
T=1; while(T--) { Work(); } return 0; }
|
*CF1651D Nearest Excluded Points
link
题意
给定一个包含平面上的n个点的集合,对集合中的每个点,找到与其曼哈顿距离最近的不在集合中的点
n≤2⋅105
思路
未在四个方向上均被包围的点(称其为边缘点),显然其答案与其相邻,容易求得。
对于非边缘点,显然其答案来自于与其曼哈顿距离最近的边缘点
考虑BFS,初始所有边缘点入队,对非边缘点进行更新,更新成功时入队即可。
Code
实际实现中使用了map,复杂度O(nlogn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <bits/stdc++.h> using namespace std; typedef pair<int,int> pii; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
const int maxn=200000+100; const int dx[]={-1,0,0,1},dy[]={0,-1,1,0}; int T; int n; pii p[maxn]; pii ans[maxn]; int vst[maxn]; map<pii,int> idx;
int dis(pii a,pii b) { return abs(a.first-b.first)+abs(a.second-b.second); }
void Work() { read(n); for(int i=1;i<=n;++i) { read(p[i].first),read(p[i].second); idx.insert({p[i],i}); } queue<int> q; for(int i=1;i<=n;++i) { for(int j=0;j<4;++j) { int X=p[i].first+dx[j],Y=p[i].second+dy[j]; if(idx.find({X,Y})==idx.end()) { ans[i]={X,Y}; q.push(i); break; } } } while(!q.empty()) { int uidx=q.front(); pii u=p[uidx]; q.pop(); for(int i=0;i<4;i++) { int X=u.first+dx[i],Y=u.second+dy[i]; if(idx.find({X,Y})!=idx.end()) { int vidx=idx[{X,Y}]; pii v=p[vidx]; if(dis(ans[uidx],v)<dis(ans[vidx],v)) { ans[vidx]=ans[uidx]; q.push(vidx); } } } } for(int i=1;i<=n;++i) printf("%d %d\n",ans[i].first,ans[i].second); }
int main() {
T=1; while(T--) { Work(); } return 0; }
|
这场的C花时间有点太多了,之前一直都很怕分情况讨论,这次就吃亏了
D这种纯属就是对算法的应用不熟悉。思维题写多了容易把算法抛之脑后,有必要加强一下才行
CF1647D Madoka and the Best School in Russia
link
这种抽丝剥茧的过程还挺过瘾的
题意
给定x,d
若一个数是d的倍数,我们称其为好的
若一个数是好的,又不能被表达成两个好的数的乘积,称其为美的
问是否存在两种将x分解成若干个美的数的乘积的方案
x,d≤109
思路
事实上,一个数是美的当且仅当其存在恰好1个d因子。
将x表示为s⋅dr=∏i=1k(ai⋅d)的形式,如果r=0,那么显然方案不存在,否则x恰好是k个美的数的乘积。
一种分解方式显然是(1⋅d)(1⋅d)⋯(1⋅d)(s⋅d),考虑构造第二种分解方案,由此我们可以进一步发现r=1时找不到第二种方案。
考察s是否为质数,如果不是,则可以将s分解为a⋅b,在上述方案中选择一个1改为a,将s改为b构造出第二种方案。
如果s是质数,那么我们必须要分解d。此时如果d是质数,那么第二种方案也是不存在的;
否则可以将d分解为m⋅n,即x=mns⋅dr−1。在此情况下若r≥4(即r−1≥3),那么我们可以构造出第二种方案(1⋅d)⋯(m⋅d)(n⋅d)(s⋅d)。
再来考虑r=2和r=3的情况。
显然r=2时我们无法构造出第二种方案(x=(mns⋅dr−1)=(s⋅d2))。
当r=3时,x可以分解成(ms⋅d)(n⋅d)或(m⋅d)(sn⋅d)。在此情况下,我们又必须要保证ms=kd或sn=kd。当ms=k1d,ns=k2d⟺d=s2时,第二种方案也是不存在的,否则即可按照上述任意一种方案进行分解。
必要性显然,下证充分性。假设d的任意分解方案d=mn,m<d,n<d都会使得ms=k1d,ns=k2d,那么有m=sk1d,n=sk2d,mn=s2k1k2d2=d,s2=k1k2d. 又s是质数,k1,k2,d中至少有一个是1,不妨k2=d.若k1=d=s,那么有m=s=d,产生矛盾,从而k1=k2=1,d=s2
取d因子复杂度为O(logx),判断质数复杂度为O(x),最终复杂度为O(logx+x+d)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <bits/stdc++.h> using namespace std; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
int T; int n,d;
void Work() { read(n),read(d); int tms=0; while(n%d==0) { ++tms; n/=d; } if(tms<=1) { puts("NO"); return; } int sqn=sqrt(n); bool isp=true; for(int i=2;i<=sqn && isp;++i) if(n%i==0) isp=false; if(!isp) { puts("YES"); return; } if(tms<=2) { puts("NO"); return; } sqn=sqrt(d); isp=true; for(int i=2;i<=sqn && isp;++i) if(d%i==0) isp=false; if(isp) { puts("NO"); return; } if(tms>=4) { puts("YES"); return; } if(d==n*n) { puts("NO"); return; } puts("YES"); }
int main() { read(T); while(T--) { Work(); } return 0; }
|
CF1657C Bracket Sequence Deletion
link
我真傻,真的
题意
给定括号串,每次从串中删去一个最短的满足以下任一条件的前缀
- 是合法括号串
- 是长度至少为2的回文串
求能够执行删除的次数和剩下的字符数量
n≤5⋅105
思路
考察当前剩余字符串的第一个字符是什么。
如果是(
,那么可以从串中直接删去一个长度为2的前缀,因为()
是合法括号序列,((
是回文串。
如果是)
,那么前缀不可能是一个合法括号串,只需考察)
后接上零或多个(
再接上一个)
构成的回文串即可。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <bits/stdc++.h> using namespace std; typedef long long ll;
template<typename T>void read(T &t) { t=0;int f=0;char c=getchar(); while(!isdigit(c)){f|=(c=='-');c=getchar();} while(isdigit(c)){t=t*10+c-'0';c=getchar();} if(f)t=-t; }
const int maxn=500000+100; int T; int n; char s[maxn];
void Work() { read(n); scanf("%s",s); int cur=0; int times=0,rest=n; while(cur<n-1) { if(s[cur]=='(') { ++times; cur+=2; rest-=2; } else { int t=cur+1; while(t<n && s[t]!=')') { ++t; } if(t<n) { ++times; rest-=t-cur+1; } cur=t+1; } } printf("%d %d\n",times,rest); }
int main() { read(T); while(T--) { Work(); } return 0; }
|
目前来看写题思路还是不够灵活,这种题手玩一下样例说不定就能找到思路的。
而且我发现自己写题的时候喜欢企图一口吃成个胖子,或许也是经常入不了手的原因吧。