导语
我愿称这一场为打表场,看电脑速度运行了属于是
涉及的知识点
打表,程序优化,找规律,树的重心
链接: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 R−L+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∣,∣c∣≤5000)满足方程 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)=∑v∈Td(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)≤∑u∈Tmin(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
*/