2019徐州(重温经典)

2019徐州(重温经典)

导语

我愿称这一场为打表场,看电脑速度运行了属于是

涉及的知识点

打表,程序优化,找规律,树的重心

链接:2019icpc徐州站

题目

A

题目大意:有1018只猫站成一排,第 i i i只猫的花费为 i i i,下标为 i i i,现在要买一个连续区间内的所有猫,初始资金为 S S S,如果买区间 [ x , y ] [x,y] [x,y]的所有猫,花费为 x ⊕ ( x + 1 ) ⋯ ⊕ y x⊕(x+1)\dots⊕y x(x+1)y,现在有 T T T个询问,每个询问给出一个区间 [ L , R ] [L,R] [L,R],代表需要在这个区间内购买,询问在这个区间内最多能买几只猫

思路:打表找规律,区间的异或和,偶数开头的区间,当区间长度为4时结果就为0,如2,3,4,5;6,7,8,9
那么,对于给定需要查询的区间,判断有多少个区间长度为4的区间即可,当区间长度大于5时,必然会出现偶数开头的长度为4的区间,那么对于区间长度小于4的直接暴力即可,大于4的,先去掉多个长度为4的区间,剩下的数字至多为4个(不包括区间,首尾奇数,4区间后余3个),压缩然后暴力即可

代码

#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
int T,s,l,r;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>T;
    while(T--) {
        int res=-1;
        cin >>l>>r>>s;
        if(r-l+1<=4) {//如果区间长度小于4
            for(int i=l; i<=r; i++)
                for(int j=i; j<=r; j++) {
                    int sum=0;
                    for(int k=i; k<=j; k++)
                        sum^=k;
                    if(sum<=s&&j-i+1>res)
                        res=j-i+1;
                }
        } else {
            int cnt=0,L[10],R[10];
            //为了方便处理,把每个点都作为区间,最多有5个数不在0区间内
            if(l%2) {//如果左边界为奇数
                L[++cnt]=l;//录入
                R[cnt]=l;
                int len=r-l,num=len/4;//获得长度为4的个数
                L[++cnt]=l+1;//记录区间左右端点
                R[cnt]=l+num*4;
                for(int i=l+1+num*4; i<=r; i++) {//处理区间末尾剩下的
                    L[++cnt]=i;
                    R[cnt]=i;
                }
            } else {
                int len=r-l+1,num=len/4;//以左边界开始,获得长度为4的个数
                L[++cnt]=l;//记录区间左右端点
                R[cnt]=l+num*4-1;
                for(int i=num*4+l; i<=r; i++) {//处理区间末尾剩下的
                    L[++cnt]=i;
                    R[cnt]=i;
                }
            }
            for(int i=1; i<=cnt; i++)
                for(int j=i; j<=cnt; j++) {
                    int sum=0;
                    for(int k=i; k<=j; k++)
                        sum^=L[k]!=R[k]?0:L[k];//记录区间异或和,左右不等表示为区间
                    if(sum<=s&&R[j]-L[i]+1>res)
                        res=R[j]-L[i]+1;//记录最值
                }
        }
        cout <<res<<endl;
    }
    return 0;
}

C

题目大意: T T T个询问,每次询问一个区间 [ L , R ] [L,R] [L,R],设区间内素数个数为 x x x,判断 x R − L + 1 < 1 / 3 \frac{x}{R-L+1}\lt 1/3 RL+1x<1/3是否成立

思路:当区间的左右端点都很大时,区间内的素数其实是很少的,可以先预处理1e6内的素数,如果数据范围不超过1e6,直接判断即可,否则,区间长度大于100,一定成立,否则暴力判断

代码

#include <bitsdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

const int MAXN=1e6+10;
int prime[MAXN/2];//存储素数
bool noprime[MAXN];//用于标记是否是素数
int biao[MAXN];
int max2=40000;
void init()
{
    memset(noprime,false,sizeof(noprime));
    biao[0]=0;
    biao[1]=1;
    int sk=0;
    for (int i=2; i<MAXN; i++)
    {
        if(!noprime[i])
            biao[i]=biao[i-1]+1;
        else
            biao[i]=biao[i-1];
        if(!noprime[i])
            prime[++sk]=i;
        for(int j=1;j<=sk&&i*prime[j]<MAXN;j++)
        {
            noprime[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
}

bool judge(int k)
{
    int p=(int) sqrt(k);
    for(int i=2;i<=p;i++)
    {
        if(k%i==0)
            return false;
    }
    return true;
}

void solve()
{
    init();
    int l,r;
    scanf("%d%d",&l,&r);
    if(r>1000000)
    {
        if(r-l>=99)
        {
            printf("Yes\n");
            return ;
        }
        int num=0;
        for(int i=l;i<=r;i++)
        {
            if(judge(i)==true)
                num++;
        }
        if(num*3<r-l+1)
            printf("Yes\n");
        else
            printf("No\n");
        return ;
    }
    int res=biao[r]-biao[l-1];
    if(res*3<r-l+1)
        printf("Yes\n");
    else
        printf("No\n");
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        solve();
    }
    return 0;
}

F

题目大意:给出一个整数 x ( x ∈ [ 0 , 200 ] ) x(x\in[0,200]) x(x[0,200]),求一组整数解 a , b , c ( ∣ a ∣ , ∣ b ∣ , ∣ c ∣ ≤ 5000 ) a,b,c(|a|,|b|,|c|\le5000) a,b,c(a,b,c5000)满足方程 a 3 + b 3 + c 3 = x a^3+b^3+c^3=x a3+b3+c3=x

思路: x x x很小,可以直接预处理0 ~ 200的结果,然后输出,需要先暴力打表,直接三重循环是不可取的,可以得知,最后的答案为两正一负或两负一正,那么只需要枚举一种情况即可,因为另一种情况是取相反数,三个都为正的情况可以被代替,那么枚举两个数,之后剩下的数在总区间内寻找即可,提交代码就不给出了,很简单

代码(打表)

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
tr1::unordered_map<ll,int> f;

ll g(ll x){
	for(ll i=-5000;i<=5000;i++)
		if (i*i*i==x) return i;

}

bool find(int x){
	for(ll i =0;i<=5000;i++)
			for(long long j=0;j<=5000;j++)
				if (f.count(i*i*i+j*j*j+x)){
					printf("a[%d] = %lld;\n",x,g(i*i*i+j*j*j+x));
					printf("b[%d] = %lld;\n",x,-i);
					printf("c[%d] = %lld;\n",x,-j);
					return true;
				}else if (f.count(i*i*i+j*j*j-x)){
					printf("a[%d] = %lld;\n",x,g(i*i*i+j*j*j-x)==0?0:-g(i*i*i+j*j*j-x));
					printf("b[%d] = %lld;\n",x,i);
					printf("c[%d] = %lld;\n",x,j);
					return true;
				}
	return false;
}

int main()
{
	freopen("ans.txt","w",stdout);
	f.clear();
	for(ll i=-5000;i<=5000;i++)
        f[i*i*i] = 1;
	for(int x=0;x<=200;x++){
		//printf("find %d\n",x );
		if (find(x)){
			printf("vis[%d] = true;\n",x);
		}
		else
			printf("vis[%d] = false;\n",x);
	}
	return 0;
}

M

题目大意:给出一棵有 N N N个节点的树,编号1到N,1为根节点,每条边的权重为1,定义 d ( u , v ) d(u,v) d(u,v)为书商两点 u , v u,v u,v间距,定义 c ( w ) = ∑ v ∈ T d ( w , v ) c(w)=\sum_{v\in T}d(w,v) c(w)=vTd(w,v) T T T为树的节点集合,对于一个点 w w w,如果 c ( w ) ≤ ∑ u ∈ T m i n ( c ( u ) ) c(w)\le \sum_{u\in T}min(c(u)) c(w)uTmin(c(u)),那么 w w w就为树 T T T的关键点,现在对于给定树的所有节点 i ∈ [ 1 , N ] i\in [1,N] i[1,N],输出所有以 i i i为根节点的子树的关键点坐标

思路:显而易见,题目是求以每个节点为根的子树的重心,那么问题就转换成如何求重心,首先,根据重心的性质,对于以当前节点 w w w为根的子树,假设其重子为 v v v w w w的重心一定在以 v v v为根的子树的重心到 w w w的路径上(证明很简单),那么,只需要求出子树 v v v的重心,之后沿着该重心向 w w w回溯即可,每次回溯一个节点,判断该点是否为重心即可,具体过程见代码

代码

#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=2e5+5;
int Size[maxn],son[maxn],head[maxn],f[maxn],n,cnt,d[maxn],root[maxn];
vector<int>res[maxn];
struct node {
    int next,to;
} e[maxn<<1];
void Add(int from,int to) {
    e[++cnt].next=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
void DFS1(int u) {//获得重子
    Size[u]=1;
    son[u]=0;
    for(int i=head[u]; ~i; i=e[i].next) {
        int v=e[i].to;
        if(v==f[u])continue;
        f[v]=u;
        d[v]=d[u]+1;
        DFS1(v);
        Size[u]+=Size[v];
        if(Size[son[u]]<Size[v])son[u]=v;
    }
}
void DFS2(int u) {
    for(int i=head[u]; ~i; i=e[i].next) {
        int v=e[i].to;
        if(v==f[u])continue;
        DFS2(v);
    }
    root[u]=u;
    if(!son[u])return ;//如果不是重子,重心即自己(轻子或叶)
    int t=root[son[u]];//获得子树的重子
    while(d[t]>d[u]&&Size[u]-Size[t]>Size[t])t=f[t];
    //按照距离条件向上跳跃,u~t的节点数量是否大于t的子节点数量
    root[u]=t;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n;
    memset(head,-1,sizeof(head));
    for(int i=0; i<n-1; i++) {//建树
        int u,v;
        cin >>u>>v;
        Add(u,v);
        Add(v,u);
    }
    DFS1(1);
    DFS2(1);
    for(int i=1; i<=n; i++) {
        int u=root[i];//获得找到的一个重心
        res[i].push_back(u);
        int t=f[u];//获得父节点,因为父节点是最有可能是另一个重心的
        if(t&&Size[i]==2*Size[u])res[i].push_back(t);
        //如果该重心的子树大小为整棵树一半,代表父节点占了剩下一半
        sort(res[i].begin(),res[i].end());
        if(res[i].size()==1)cout <<res[i][0]<<endl;
        else cout <<res[i][0]<<" "<<res[i][1]<<endl;
    }
    return 0;
}
/*
4
1 2
2 3
2 4
*/

参考文献

  1. 2019 ICPC徐州现场赛 A. Cat 思维规律题
  2. Kill the Tree(树的重心)
  3. 2019 ICPC 徐州区域赛 - F The Answer to the Ultimate Question of Life, The Universe, and Everything.(打表)
  4. Kill the tree【2019 徐州】【重心的性质】
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值