省选模拟测试5

期望得分: \(100+40+30 = 170\)

实际得分: \(100+0+0 = 100\)

\(T1\) \(noip\) 难度的题,不说了。

\(T2\) 虽然正解是线段树合并,但自己却拿线段树合并优化暴力 \jk。

\(T3\) 本来打了 \(30\) 分的暴力分,但是出题人没造那一档的数据,然后我就愉快的爆零了。

T1 茶 a

题意描述

一开始,你拥有一个饼干,现在有三种操作

  1. 一种是将手里的饼干数量 \(+1\)
  2. 一种是将手里的 \(a\) 个饼干换成 1¥
  3. 一种是将 1¥换成 \(b\) 个饼干

求出在 \(k\) 次操作后最多有多少个饼干。

数据范围: \(k,a,b\leq 10^9\)

solution

水题,不说了。

注意特判各种情况就行。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
int k,a,b;
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
signed main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    k = read(); a = read(); b = read();
    if(a >= b || k <= a) printf("%lld\n",k+1);
    else 
    {
        int tmp = a + (k-a+1)/2 * (b-a);
        if((k-a+1)%2 == 1) tmp++;
        printf("%lld\n",max(k+1,tmp));  
    }
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 民族 b

题意描述

\(n\) 座城市,一些民族。这些城市之间由 \(n-1\) 条有边权道路连接形成了以城市 \(1\) 为根的有根树。
每个城市都是某一民族的聚居地,已知第 \(i\) 个城市的民族是 \(a_i\),人数是 \(1\)
我们定义一个民族 \(x\) 的来往程度 \(f(x)\) 为民族为 \(x\) 的点两两之间的距离之和,距离定义为树上两点间
最短路距离。
他想知道以 \(i\) 为根的子树内来往程度最大的民族 \(x\) 是多少,如果有多个,求编号最小。
以及对于给定的 \(k_i\) ,求 子树内编号的 \(k_i\) 小民族 \(y\)\(f(y)\)

数据范围: \(n\leq 10^5, a_i\leq 10^5\)

solution

线段树合并/DSU。

我们把距离柿子拆一下变成: \(dis[x]+dis[y]-2\times dis[lca(x,y)]\) , \(dis[i]\) 表示根节点到 \(i\) 的边权和。

我们考虑那线段树合并来维护这个东西。

我们对于每个节点,开一个以民族编号为下标的权值线段树。

对于每个叶子节点 \(o\) ,设其民族编号为 \(x\), 维护如下信息:

  • \(sum[o]\) 表示 \(f(x)\) 即民族编号为 \(x\) 的来往程度。
  • \(w[o]\) 表示所有民族编号为 \(x\) 的点的 \(dis\) 之和。
  • \(siz[o]\) 表示民族编号为 \(x\) 的点的个数。
  • \(tag[o]\) 表示民族编号为 \(x\) 的点是否出现过。

对于非叶子节点 \(o\) 维护如下信息:

  • \(sum[o]\) 表示 \(o\) 的子孙结点中叶子节点的 \(sum\) 最大值。
  • \(id[o]\) 表示 \(o\) 的子孙节点中 \(sum\) 最大的叶子结点的编号是多少。
  • \(tag[o]\) 表示 \(o\) 的子孙节点中叶子结点不为空的个数。

对于线段树合并的时候,我们只需要在叶子节点合并,其他的非叶子节点由儿子节点 \(up\) 一下即可。

假如说我们现在要把 \(rt[x]\)\(rt[to]\) 两颗线段树合并,对两颗线段树的叶子节点 \(p,q\) 合并时,设合并之后的叶子节点为 \(p\) ,则有:

  • \(sum[p] = sum[p] + sum[q] + siz[p]\times sum[q] + siz[q]\times sum[p] - 2\times siz[p]\times siz[q]\times dis[x]\)
  • \(w[p] = w[p] + w[q]\)
  • \(siz[p] = siz[p] + siz[q]\)
  • \(tag[p] = tag[p] \ or \ tag[q]\)

我们只需要遍历一遍整棵树,维护出上述信息,对于询问 \(1\) 就是 \(tr[rt[x]].id\) ,对于询问 \(2\) ,我们只需要利用维护出的 \(tag\) 在线段树上二分即可。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5+10;
int n,cnt,tot,u,v,w,maxn;
int a[N],k[N],rt[N],head[N],sum[N],ans1[N],ans2[N];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
struct node
{
    int to,net,w;
}e[N<<1];
struct Tree
{
    int lc,rc;
    int sum,siz,w,id,tag;
}tr[10000010];
void add(int x,int y,int w)
{
	e[++tot].to = y;
	e[tot].w = w;
	e[tot].net = head[x];
	head[x] = tot;
}
void up(int o)
{
    tr[o].tag = tr[tr[o].lc].tag + tr[tr[o].rc].tag;
    tr[o].sum = max(tr[tr[o].lc].sum,tr[tr[o].rc].sum);
    if(tr[o].sum == 0)
    {
        if(tr[tr[o].lc].tag) tr[o].id = tr[tr[o].lc].id;
        else tr[o].id = tr[tr[o].rc].id;
    }
    else tr[o].id = tr[o].sum == tr[tr[o].lc].sum ? tr[tr[o].lc].id : tr[tr[o].rc].id;
}
void insert(int &o,int l,int r,int x,int val)
{
    if(!o) o = ++cnt;
    if(l == r)
    {
        tr[o].w += val;
        tr[o].siz++;
        tr[o].tag = 1;
        tr[o].id = l;
        return;
    }
    int mid = (l+r)>>1;
    if(x <= mid) insert(tr[o].lc,l,mid,x,val);
    if(x > mid) insert(tr[o].rc,mid+1,r,x,val);
    up(o);
}
void merage(int &x,int y,int l,int r,int root)
{
    if(!x) {swap(x,y); return;}
    if(!y) return;
    if(l == r)
    {
        tr[x].sum = tr[x].sum + tr[y].sum + tr[x].w * tr[y].siz + tr[y].w * tr[x].siz - 2 * sum[root] * tr[x].siz * tr[y].siz;
        tr[x].siz += tr[y].siz;
        tr[x].tag |= tr[y].tag;
        tr[x].w += tr[y].w;
        return;
    }
    int mid = (l+r)>>1;
    merage(tr[x].lc,tr[y].lc,l,mid,root);
    merage(tr[x].rc,tr[y].rc,mid+1,r,root);
    up(x);
}
int query(int o,int l,int r,int k)
{
    if(l == r) return tr[o].sum;
    int mid = (l+r)>>1;
    int num = tr[tr[o].lc].tag;
    if(k <= num) return query(tr[o].lc,l,mid,k);
    else return query(tr[o].rc,mid+1,r,k-num);
}
void dfs1(int x,int fa,int w)
{
    sum[x] = sum[fa] + w;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa) continue;
        dfs1(to,x,e[i].w);
    }
}
void dfs2(int x,int fa)
{
    insert(rt[x],1,maxn,a[x],sum[x]);
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa) continue;
        dfs2(to,x);
        merage(rt[x],rt[to],1,maxn,x);
    }
    ans1[x] = tr[rt[x]].id;
    if(tr[rt[x]].tag < k[x]) ans2[x] = -1;
    else ans2[x] = query(rt[x],1,maxn,k[x]);
}
signed main()
{
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    n = read();
    for(int i = 1; i <= n-1; i++)
    {
        u = read(); v = read(); w = read();
        add(u,v,w); add(v,u,w);
    }
    dfs1(1,0,0);
    for(int i = 1; i <= n; i++) a[i] = read(), maxn = max(maxn,a[i]);
    for(int i = 1; i <= n; i++) k[i] = read();
    dfs2(1,0);
    for(int i = 1; i <= n; i++) printf("%lld %lld\n",ans1[i],ans2[i]);
    fclose(stdin); fclose(stdout);
    return 0;
}

T3 c

题意描述

给你三个整数 \(k,a,b\) ,满足 \(\gcd(a,b) = 1\) ,让你求不能被 \(ax+by, x\geq 0, y\geq 0\) 表示出来的第 \(k\) 大的数是多少。

数据范围:\(a,b\leq 5\times 10^7,k\leq 10^{18}\)

solution

首先有一个经典的结论:不能被 \(ax+by\) 表示出来的最大的数是 \(a\times b-a-b\) ,不能被表示出来的数的个数为 \((a-1)\times (b-1)\over 2\)

具体证明的话,我不太会证。

对于这道题我们可以先转化为求第 \(k\) 小的是多少,即: \(k = {(a-1)\times (b-1)\over 2}-k+1\)

我们把 \([1,a\times b-a-b]\) 分成若干块,每一块的长度均为 \(a\) ,如图:

我们可以确定第 \(k\) 小的是在那一块,具体来说就是求一个 \(pos\) 使得 \(1-pos\times a\) 中不能被表示出来的数 \(\geq k\), \(1-(pos-1)\times a\) 中不能被表示出来的数 \(\leq k\)

那么我们怎么求 \(1-ia\) 中不能被表示出来的数的个数呢?这个我们可以转化为求能被表示出来的个数。

具体来说就是,设 \(sum[i]\) 表示 \(1-i\times a\) 中能被表示出来的数的个数。

则有递推式 :\(sum[i] = sum[i-1] + now + 1\), 其中 \(now\)\(1-i\times a\) 中为 \(b\) 的倍数有多少个。

具体证明如下,首先 \([1,i\times a]\) 中能被表示出来的数的个数等价于 \([(i-1)\times a+1,i\times a]\) 这一段区间能被表示出来的数的个数加上 \([1,(i-1)\times a]\) 这一段区间能被表示出来的个数。

对于 \([1,(i-1)\times a]\) 这一段区间能被表示出来的数的个数就是 \(sum[i-1]\)

对于 \(by_1\) ,我们可以使他加上 \(ax1\) 使得 \((i-1)\times a \leq ax_1+by_1\leq i\times a\)

这样的 \(by_1\) 共有 \(now\) 个,又因为 \(\gcd(a,b) = 1\) ,可知 \(ax_1+by_1\) 是不会重复的。

所以 \([(i-1)\times a,i\times a]\) 这一段区间可以被表示出来的数的个数为 \(now+1\), \(+1\) 则是因为 \(i\times a\) 也可以被表示出来。

因此,我们的递推式 \(sum[i] = sum[i-1]+now+1\) 是正确的。

那么有了 \(sum[i]\) ,我们的 \(pos\) 就很好求出来了。

求出来 \(pos\) 之后,我们先用 \(k\) 减去 \([1,(pos-1)\times a]\) 中不能被表示出来的数的个数,然后 \([(pos-1)\times a+1,pos\times a]\) 中第 \(k\) 个不能被表示出来的数,就是我们的答案。

又因为每一块的元素不超过 \(a\) 个,所以我们可以枚举 \([(pos-1)\times a+1,pos\times a]\) 这一段区间中的数,来判断它是否是第 \(k\) 个不能被表示出来的数。

那么怎么判断数 \(s\) 是否可以被表示出来呢?

很简单,因为 \(s\) 可以被表示出来,等价于 \(s=ax+by\) 即: \(s\equiv by\pmod a\) ,所以我们在枚举 \(b\) 的倍数的时候把 \(by\% a\) 标记为 \(1\), 如果 \(s\%a\) 被标记过,那么 \(s\) 就可以被表示出来。

然后我们这道题就做完了。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define int long long
int a,b,k,cnt,now,sum,pos;
bool vis[50000010];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
signed main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    a = read(); b = read(); k = read();
    k = ((a-1)*(b-1)/2)-k+1; vis[0] = 1;
    for(int i = 0; ; i++)
    {
    	while((now+1)*b <= i*a) now++, vis[(now*b)%a] = 1;
    	if(i*a-sum-now >= k) 
    	{
    		pos = i;
    		break;
		}
		sum += now + 1;
	}
	k -= (pos-1)*a-sum+1;//这里+1是因为0也可以被表示出来
	for(int i = (pos-1)*a+1; i <= pos*a; i++)
	{
		if(!vis[i%a]) k--;
		if(k == 0) 
		{
			printf("%lld\n",i);
			return 0;
		}
	}
	fclose(stdin); fclose(stdout);
    return 0;
}
posted @ 2021-03-05 07:45  genshy  阅读(70)  评论(0编辑  收藏  举报