题目地址:https://codeforces.com/contest/1338
A Powered Addition
题意:
思路:
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
LL a[maxn];
int n;
int get(LL x,LL y)
{
LL q=x-y;
REP_(i,40,0) if(q&(1ll<<i)) return i;
return -1;
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
n=read();
REP(i,1,n) a[i]=read();
int ans=-1;
REP(i,1,n-1) if(a[i]>a[i+1])
{
int x=get(a[i],a[i+1]);
ans=max(ans,x);
a[i+1]=a[i];
}
printf("%d\n",ans+1);
}
return 0;
}
B Edge Weight Assignment
题意:有一棵树,现在要给每条边分配一个边权,使得最后满足:所有叶子结点两两之间路径边权异或和为 0 。问所有分配方案中,边权不同的数目最多和最少是多少?(保证一定存在分配方案)
思路:如果选择一个叶子结点当做根结点,题目等价于所有其他叶子结点到根的边权异或和都为 0 。
先看最小的数目:如果某条根-叶路径的长度为偶数,那么全部分配一样的数就行了;如果是奇数,那么至少要 3 个不一样的数;所以这个答案事实上就是看叶结点深度有没有奇数。
然后是最大的数目:对于某个子树的根结点 u,它的度为1的儿子(也就是叶子)与 u 的连边的那个数一定都是一样的,而还有儿子的儿子就可以继续 dfs。但是这里还有个细节,如果叶子儿子的深度为 2,那就说明它还必须和 u 上面那条边相等。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
vector<int> G[maxn];
int n,du[maxn],root=1,ansmin=1,ansmax=0,d[maxn];
set<int> ds;
void dfs1(int u,int fa,int dd)
{
d[u]=dd;
if(G[u].size()==1 && fa)
{
ds.insert(dd);
return;
}
REP(i,0,G[u].size()-1)
{
int v=G[u][i];
if(v==fa) continue;
dfs1(v,u,dd+1);
}
}
void dfs2(int u,int fa)
{
int flag=0;
REP(i,0,G[u].size()-1)
{
int v=G[u][i];
if(v==fa) continue;
if(G[v].size()==1 && d[v]!=2) flag=1;
else if(G[v].size()>1) ansmax++, dfs2(v,u);
}
if(flag) ansmax++;
}
int main()
{
//freopen("input.txt","r",stdin);
n=read();
REP(i,1,n-1)
{
int u=read(),v=read();
G[u].push_back(v);
G[v].push_back(u);
du[u]++; du[v]++;
}
while(du[root]>1) root++;
dfs1(root,0,0);
for(set<int>::iterator i=ds.begin();i!=ds.end();i++)
if(*i&1) ansmin=3;
dfs2(root,0);
printf("%d %d",ansmin,ansmax);
return 0;
}
C Perfect Triples
题意:在正整数集中,每次选出字典序最小的三个数 (a, b, c),使得 ,并且 a、b、c 之前都没有选择过。把每次选的 (a, b, c) 加入队列末尾。然后有 T 次询问(1e5),每次询问给出一个正整数 n(1e16) ,要求给出队列中第 n 个数是多少?
思路:好吧其实我是打印出来找到了规律:(1)第一位的规律是 1
, 4 5 6 7
, 16 17 ... 31
, … (2)第二位的规律是 2
, 8 10 11 9
, 32 34 35 33 ...
, …,其中第二位后面满足 以四分割,从前往后分别是 x, x+2, x+3, x+1 这样的规律;(3)第三位其实就是前两位的异或。然后找到规律后就写出找第一位和第二位的函数就好了,其中第二位要用分治。
后来看了些其他博客,发现这是一个四进制的特点:1,2,3的两两异或其实是一个闭包(1^2=3, 1^3=2, 2^3=1)。然后也很容易找出规律(对于 a 的四进制的每一位,(a, b, c) 只有可能是 (0, 0, 0), (1, 2, 3), (2, 3, 1), (3, 1, 2)),那就只用求出 a 就可以了。
代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
LL mi4(int k) {return 1ll<<(k*2);}
LL get1(LL n)
{
LL x=0,k=0;
while(x<n) x+=mi4(k++);
k--; x-=mi4(k); x++; n-=x;
return mi4(k)+n;
}
LL get2(LL xl,LL xr,LL ll,LL rr,LL n)
{
if(xl+1>=xr) return ll;
LL l[4],r[4],x=(rr-ll)/4;
l[0]=ll; r[0]=ll+x;
l[1]=ll+2*x; r[1]=ll+3*x;
l[2]=ll+3*x; r[2]=ll+4*x;
l[3]=ll+x; r[3]=ll+2*x;
LL q=(n-xl)/x;
return get2(xl+x*q,xl+x*(q+1),l[q],r[q],n);
}
LL get2(LL n)
{
LL l=2,r=3,xl=1,xr=2,k=0;
while(!(n>=xl && n<xr))
{
l*=4; r*=4; k++;
xl=xr; xr+=mi4(k);
}
return get2(xl,xr,l,r,n);
}
LL get(LL n)
{
if(n%3==1) return get1(n/3+1);
else if(n%3==2) return get2(n/3+1);
else return get1(n/3)^get2(n/3);
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
LL n;
scanf("%lld",&n);
printf("%lld\n",get(n));
}
return 0;
}
D Nested Rubber Bands
题意:给定一棵树,现在把每个结点变成一个闭合不自交曲线,并且满足:两个闭合曲线相交当且仅当原来的树上两个结点有连边。问在所有变化方式中,变之后存在的嵌套个数最多是多少个(嵌套是指一个闭合曲线包含另一个……)
思路:dfs+dp。对于每个结点定义 f(i, j) 表示结点 i 作为(j=1)嵌套中的一个或者不作为(j=0)嵌套中的一个时,其子树的最大嵌套数。每个子树的嵌套情况一定如下(左边那个中间其实还有很多,不过省略了):
其中如果子树根结点不作为嵌套,则上图中右边那个是根结点;如果子树根结点作为嵌套,则左边那个是根结点。
那么状态转移公式就有:
(这是通过画图得出来的:上面那个就是用 u 去交上一个最大的儿子,然后其它儿子都与 u 相交并且套在最大儿子的外面;下面那个就是用 u 去套最大儿子的根,因为要求 u 也要在嵌套内)
但是要注意到,上面的状态转移公式仅仅针对形成上面那张图,意思就是 f(i, j) 的意义下,某个子树的根一定在最外层(要么是上图左边,要么是右边),但要是考虑求最优答案时,还需要实时考虑如果当前的根在内层,去更新最优答案,这个通过画图就可以得到:(1)如果当前 u 不在嵌套,那么最优是选择最大的两个儿子相互嵌套,然后 u 去连接他们,同时 u 的其它儿子与 u 相连套在里面的大儿子上;(2)如果当前 u 在嵌套,那么最优是选择两个最大的根不嵌套的儿子,用 u 去嵌套地连接这两个儿子。
那么就 dp 计算 f,实时更新 ans 就行了。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
vector<int> G[maxn];
int f[maxn][2],n,ans;
void dfs(int u,int fa)
{
if(fa && G[u].size()==1) {f[u][1]=1; return;}
for(int v:G[u]) if(v!=fa)
{
dfs(v,u);
ans=max(ans,f[u][1]+max(f[v][0],f[v][1])+(int)(G[u].size()>1?G[u].size()-2:0));
ans=max(ans,f[u][0]+f[v][0]+1);
f[u][0]=max(f[u][0],f[v][0]);
f[u][1]=max(f[u][1],max(f[v][0],f[v][1]));
}
swap(f[u][0],f[u][1]);
f[u][0]+=(G[u].size()>1?G[u].size()-2:0);
f[u][1]++;
}
int main()
{
//freopen("input.txt","r",stdin);
n=read();
REP(i,1,n-1)
{
int u=read(),v=read();
G[u].push_back(v); G[v].push_back(u);
}
dfs(1,0);
cout<<ans;
return 0;
}
E
题意:
思路:
代码: