标签:组合 down i++ 分类方法 题解 hide 统计 col pow
枚举
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; const int MAXN=1e5+10; int n,m,dat[MAXN],res=1<<30; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d",&dat[i]); sort(dat+1,dat+m+1); for(int i=1;i<=m-n+1;i++) res=min(res,dat[i+n-1]-dat[i]); printf("%d",res); return 0; }
可以发现分母为$max(a*d,b*c)$,分子为$|a*d-b*c|$
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; int a,b,c,d,ta,tb,tc,td,resx,resy; int GCD(int x,int y){return x%y==0?y:GCD(y,x%y);} int main() { scanf("%d%d%d%d",&a,&b,&c,&d); resx=abs(a*d-c*b),resy=max(a*d,c*b); int gcd=GCD(resx,resy); printf("%d/%d",resx/gcd,resy/gcd); return 0; }
直接贪心,考虑将空格插入连续的序列中
如果可以不翻倍自然答案就是$m$,否则应当尽可能在前面翻倍来减少增加的分数
能推出翻倍$q$次后的分数为$(2^q-1)*2*k$,这样再加上剩余不翻倍的分数即可
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; const ll MOD=1e9+9; ll n,m,k,res; ll quick_pow(ll a,ll b) { ll ret=1; for(;b;b>>=1,a=a*a%MOD) if(b&1) ret=ret*a%MOD; return ret; } int main() { scanf("%I64d%I64d%I64d",&n,&m,&k); if(!m||!n) return puts("0"),0; ll rst=n-m,num=n/k; if(rst>=num) res=m; else res=(quick_pow(2,num-rst)-1)*2*k%MOD+(m-(num-rst)*k%MOD); printf("%I64d",(res+MOD)%MOD); return 0; }
官方题解:
将原树转化成有根树,对于一个点的最远关键点分成两类:
1、在该点的子树中,记最远距离为$MaxDown$
2、在树上的其它部分,记最远距离为$MaxUp$
对于1,明显可以$dfs$通过孩子节点来更新
对于2,又要分为两类来更新:
1、在其兄弟节点的子树中,即$MaxDown_{sibling}+2$
2、不在父亲节点的子树中,即$MaxUp_{father}+1$
其中注意用兄弟节点更新时要先记录最大/次大值来保证$O(1)$更新
不过感觉我的方法更简单啊……
先想想如何求一个点到树上任意点的最远距离
明显是用数的直径来解决的经典问题,可用反证法证明最远点一定是直径的两端
发现将任意点改为特定的关键点后该结论依然成立!
因此只要找到关键点中最远的点对$(a,b)$,对于每个点$v$判断$dist(a,v),dist(b,v)\le d$
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; const int MAXN=1e5+10; int head[MAXN],tot; struct edge{int nxt,to;}e[MAXN<<2]; int n,m,d,x,y,p[MAXN],rt1,rt2,d1[MAXN],d2[MAXN],res; void add(int from,int to) {e[++tot].nxt=head[from];e[tot].to=to;head[from]=tot;} void dfs(int x,int anc,int *d) { for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=anc) d[e[i].to]=d[x]+1,dfs(e[i].to,x,d); } int main() { scanf("%d%d%d",&n,&m,&d); for(int i=1;i<=m;i++) scanf("%d",&p[i]); for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x); d1[1]=0;dfs(1,0,d1); for(int i=1;i<=m;i++) if(d1[p[i]]>d1[rt1]) rt1=p[i]; d1[rt1]=0;dfs(rt1,0,d1); for(int i=1;i<=m;i++) if(d1[p[i]]>d1[rt2]) rt2=p[i]; d2[rt2]=0;dfs(rt2,0,d2); for(int i=1;i<=n;i++) if(d1[i]<=d&&d2[i]<=d) res++; printf("%d",res); return 0; }
虽说第一种解法可能代码量稍大,但分类方法还是很实用的:
将无根树转化成有根树后按照是否在该点的子树中分类
其中兄弟节点对该点的贡献不要忘记统计!我一开始就漏掉了……
发现最优解除了叶子节点和根外,其它节点必然都属于$a$
考虑一个点向当前树插入,其要么接在某个节点后,要么重开一棵树
由于$n$只有8,明显排序预处理后枚举各种组合就好了
其中有一些技巧需要注意:
1、采取从根到叶子的构造更加方便
由于树根确定其叶子节点就已经确定,后面接点时就不用再考虑叶子节点的贡献了
2、不要漏考虑最大点本身就是根的情况,这样答案会减少1
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; ll n,dat[10],dvs[10],res=1ll<<60; void dfs(int k,int sum,int rt) { if(k==n+1) {res=min(res,sum+ll(rt>1));return;} if(dvs[k]>1) sum++; dfs(k+1,sum+dvs[k],rt+1);//单独成树 for(int i=1;i<k;i++)//连在之前的节点后 if(dat[i]%dat[k]==0) dat[i]/=dat[k],dfs(k+1,sum,rt),dat[i]*=dat[k]; } int main() { scanf("%I64d",&n); for(int i=1;i<=n;i++) scanf("%I64d",&dat[i]); sort(dat+1,dat+n+1,greater<ll>()); for(int i=1;i<=n;i++) { ll t=dat[i]; for(int j=2;1ll*j*j<=dat[i];j++) while(t%j==0) t/=j,dvs[i]++; if(t>1) dvs[i]++; } dfs(1,0,0); printf("%I64d",res); return 0; }
标签:组合 down i++ 分类方法 题解 hide 统计 col pow
原文地址:https://www.cnblogs.com/newera/p/9522968.html