题解
蛮难的今天,题面还有错,SDOI题。
第一题——直径(diameter)
【题目描述】
- 求出给出带点权树上最长链满足链上gcd不等于1。
- 题面刚开始错了,求的是链上两两不互质,那就只有 O(n3) O ( n 3 ) 暴力才能做…后来标答第一句就和题意不一样我…
- 说一下标答,其实就是求树上直径的变形,加上了gcd的限制,枚举每一个质因数,然后将能将它的倍数的点都标记,求出每一部分子树的最长半径。
- 然后求出最长半径,就是从根搜到离根最远的节点,然后将这个点设为新根,从新根再搜到离他最远的节点(证明自己yy一下?)就是最长半径。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <vector>
using namespace std;
void fff(){
freopen("diameter.in","r",stdin);
freopen("diameter.out","w",stdout);
}
const int N=100100;
struct Edge{
int to,nxt;
}e[N<<1];
map<int,vector <int> > M;
int a[N],head[N],tot=0;
void add(int u,int v){
e[++tot].nxt=head[u];
e[tot].to=v;
head[u]=tot;
}
int n;
int ans;
int prime[N],vis[N],cnt=0;
int pt,tmax;
bool visited[N];
void init(){
for (int i=2;i<=100000;i++){
if(!visited[i]) prime[++cnt]=i;
for (int j=1;j<=cnt&&i*prime[j]<=100000;j++){
visited[i*prime[j]]=true;
if(i%prime[j]==0) break;
}
}
}
void dfs(int u,int f,int p,int d){
vis[u]=p;if(d>tmax) tmax=d,pt=u;
for (int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=f&&a[v]%p==0) dfs(v,u,p,d+1);
}
}
void expl(int u,int f,int p,int d){
if(d>ans) ans=d;
for (int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=f&&a[v]%p==0) expl(v,u,p,d+1);
}
}
int main(){
fff();
init();
ans=0;
scanf("%d",&n);
bool flag=false;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
if(u!=i&&v!=i) flag=true;
add(u,v);
add(v,u);
}
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
int x=a[i];
for (int j=1;j<=cnt&&1ll*prime[j]*prime[j]<=x;j++){
if(x%prime[j]==0){
while (x%prime[j]==0) x/=prime[j];
M[prime[j]].push_back(i);
if(x==1) break;
}
if(x!=1) M[x].push_back(i);
}
}
for (map<int,vector <int> >::iterator s=M.begin();s!=M.end();s++){
int p=(*s).first;
vector <int> v=(*s).second;
for (vector <int>::iterator u=v.begin();u!=v.end();u++){
if(vis[*u]!=p){
tmax=0;
dfs(*u,0,p,0);
expl(pt,0,p,1);
}
}
}
cout<<ans;
}
第二题——选举(vote)
【题目描述】
- 给出n个人的不完全统计选票 ci c i ,总票数为v,通过m轮评奖,要求出每个人获得奖项的最多个数和最小个数,票数小于总票数的5%直接淘汰。
- 评奖的权值为 ai/(bi+1) a i / ( b i + 1 ) 的最大值, ai a i 为当前票数, bi b i 为已获奖数。权值最大的获奖。
- 有两问,第一问很好解决,求最多次数的一定要让这个人的票数尽量多,这个可以用堆或者暴力维护。
第二问比较难搞,因为不一定是平均情况下就是最少的,所以需要利用dp,令 f[i][j] f [ i ] [ j ] 表示前 i i 个人至少拿j个奖的至少加的票数,那么
其中 calc(i,k) c a l c ( i , k ) 是 i i 拿个奖最少加的票数,那么对比于最终查询的x来说,前k轮的权值都要大于x获得mid个奖项之后的权值。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
void fff(){
freopen("vote.in","r",stdin);
freopen("vote.out","w",stdout);
}
const int N=110;
int n,m,v,sum=0;
int c[N];
typedef pair<int,int> ii;
struct node{
int a_val,b_val;
double val;
int num;
bool operator < (const node x)const{
if(val==x.val) return num>x.num;
return val<x.val;
}
}a[N];
int big_ans[N],minn_ans[N],cnt[N];
priority_queue < node > Heap;
pair<int,int> cc[N];
int f[210][210];
bool check(int x,int y){
for (int i=0;i<=m;i++) f[0][i]=1<<30;
f[0][0]=0;
for (int i=1;i<=n&&i<=21;i++){
if(i==x){
for (int j=0;j<=m;j++) f[i][j]=f[i-1][j];
continue;
}
for (int j=0;j<=m;j++) f[i][j]=1<<30;
for (int j=0;j<=m;j++){
for (int k=0;k<=j;k++){
int cur=(cc[x].first*k)/(y+1)-cc[i].first;
if(k && 1LL*(cc[i].first+cur)*(y+1)==1LL*cc[x].first*k && cc[i].second>cc[x].second) cur++;
if(cur<0) cur=0;
if(k&&20LL*(cc[i].first+cur)<v) cur=(v+19)/20-cc[i].first;
f[i][j]=min(f[i][j],f[i-1][j-k]+cur);
}
}
}
return f[min(n,21)][m-y]<=v-sum;
}
int main(){
fff();
scanf("%d%d%d",&v,&n,&m);
memset(minn_ans,0x3f3f3f,sizeof(minn_ans));
for (int i=1;i<=n;i++) scanf("%d",&c[i]),sum+=c[i],a[i].num=i;
for (int i=1;i<=n;i++){
while (!Heap.empty()) Heap.pop();
memset(cnt,0,sizeof(cnt));
for (int j=1;j<=n;j++)
if(j!=i){
a[j].a_val=c[j];
a[j].b_val=0;
a[j].val=(double)(a[j].a_val)/(double)(a[j].b_val+1);
if(a[j].a_val<(int)(v*0.05))
minn_ans[j]=0;
else Heap.push(a[j]);
}
a[i].a_val=c[i]+v-sum;
a[i].b_val=0;
a[i].val=(double)(a[i].a_val)/(double)(a[i].b_val+1);
if(a[i].a_val>=(int)(v*0.05)) Heap.push(a[i]);
else{
big_ans[i]=0;
continue;
}
int tmp=m;
while (tmp--){
node t=Heap.top();Heap.pop();
cnt[t.num]++;
t.b_val++;
t.val=(double)(t.a_val)/(double)(t.b_val+1);
Heap.push(t);
}
for (int j=1;j<=n;j++){
big_ans[j]=max(big_ans[j],cnt[j]);
}
}
for (int i=1;i<=n;i++) printf("%d ",big_ans[i]);
printf("\n");
for (int i=1;i<=n;i++) cc[i]=make_pair(c[i],i);
sort(cc+1,cc+n+1,greater<ii>());
for (int i=1;i<=n;i++){
if(20LL*cc[i].first<v) break;
int l=0,r=m,mid;
while (l<=r){
check(i,mid=l+r>>1)?r=(minn_ans[cc[i].second]=mid)-1:l=mid+1;
}
}
for (int i=1;i<=n;i++) printf("%d ",minn_ans[i]);
}
第三题——K 树(Ktree)
【题目描述】
- 给出一颗树,求出最大的k使得存在 u,v(u!=v) u , v ( u ! = v ) ,满足两个节点下的k个节点组成的子树完全相等。相等表示子树的位置形状完全一样。
- 第一眼就知道是树上hash。加上可并堆进行合并生成新的hash值。
- 需要注意的是,这里的可并堆不一定是左偏树(会导致左右不平衡之后带来整个弹出的超时)。
用一个 p 进制数(p>n)来表示每一个节点,这个数有 depth 位,depth 为这个节点在子树中的深度,这个数在 p 进制下从低到高第 i 位表示这个点的 depth-i 级祖先是其父亲的第几个儿子。
然后把距离大于k的弹出就可以了。
#pragma GCC optimize(3)
#pragma G++ optimize(3)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
#define LL long long
using namespace std;
void fff(){
freopen("Ktree.in","r",stdin);
freopen("Ktree.out","w",stdout);
}
const int N=100100,base=100003;
int n,cnt=0;
vector <int> son[N];
map<LL,int> M;
int depth[N];
inline void rea(int &x){
char c=getchar(); x=0;
for(;c>'9'||c<'0';c=getchar());for(;c>='0'&&c<='9';x=x*10+c-'0',c=getchar());
}
struct iheap{
iheap *l,*r;
LL val,mul,add,key;
int deep,size;
}h[N],*rt[N];
inline void dfs(int u){
for (int i=0;i<(int)son[u].size();i++)
depth[son[u][i]]=depth[u]+1,dfs(son[u][i]);
}
inline void Mul(iheap *x,LL y){
if(!x) return;
x->val*=y;x->mul*=y;x->add*=y;x->key*=y;
}
inline void Add(iheap *x,LL y){
if(!x) return;
x->val+=y*x->size;x->add+=y;x->key+=y;
}
inline void Push(iheap *x){
if(x->mul!=1) Mul(x->l,x->mul),Mul(x->r,x->mul),x->mul=1;
if(x->add) Add(x->l,x->add),Add(x->r,x->add),x->add=0;
}
inline void Up(iheap *x){
x->size=(x->l?x->l->size:0)+(x->r?x->r->size:0)+1;
x->val=(x->l?x->l->val:0)+(x->r?x->r->val:0)+x->key;
}
inline int ran(){
static int x=50125012;x+=(x<<4)+1;return x&65536;
}
iheap *Merge(iheap *x,iheap *y){
if(!x||!y) return !x?y:x;
if(x->deep<y->deep) return Merge(y,x);
Push(x),Push(y);
#ifdef LOCAL
int ppp;
(ppp=ran())?x->l=Merge(x->l,y):x->r=Merge(x->r,y);
printf("%d\n",ppp);
#else
ran()?x->l=Merge(x->l,y):x->r=Merge(x->r,y);
#endif
return Up(x),x;
}
bool check(int u,int k){
for (int i=0;i<(int)son[u].size();i++){
if(check(son[u][i],k)) return true;
Mul(rt[son[u][i]],base);
Add(rt[son[u][i]],i+1);
rt[u]=Merge(rt[u],rt[son[u][i]]);
}
h[++cnt].val=h[cnt].key=0;h[cnt].deep=depth[u];
h[cnt].mul=1;h[cnt].add=0;h[cnt].l=h[cnt].r=0;h[cnt].size=1;
rt[u]=Merge(rt[u],h+cnt);
while (rt[u]&&rt[u]->deep>depth[u]+k)
Push(rt[u]),rt[u]=Merge(rt[u]->l,rt[u]->r);
if(!rt[u]||rt[u]->deep!=depth[u]+k) return false;
return M.count(rt[u]->val)?true:(M[rt[u]->val]=1,false);
}
void clean(){
M.clear();cnt=0;
for(int i=1;i<=n;i++) rt[i]=0;
}
int main(){
#ifndef LOCAL
fff();
#endif
rea(n);
for(int i=1;i<=n;i++){
int x,v;
rea(x);
for (int j=1;j<=x;j++) rea(v),son[i].push_back(v);
}
dfs(1);
int L=1,R=n,mid,ans=0,sx=0;
while (L<=R){
clean();
if(check(1,mid=(L+R)>>1)) L=(ans=mid)+1;
else R=mid-1;
}
printf("%d\n",ans);
return 0;
}