视频讲解:BV1n54y1L7Nv
给定整数 n ( 1 ≤ n ≤ 1 0 9 ) n(1 \leq n \leq 10^9) n(1≤n≤109) ,求最大的整数 k k k ,使得
n & ( n − 1 ) & ( n − 2 ) & ( n − 3 ) & . . . ( k ) = 0 n\&(n-1)\&(n-2)\&(n-3)\&...(k)=0 n&(n−1)&(n−2)&(n−3)&...(k)=0
一般遇到这种题,可以考虑打表找规律,然后发现小于 n n n 的最大的 2 m − 1 2^m-1 2m−1 即是答案。
具体证明如下:
#include
using namespace std;
int main()
{
int T,n,ans;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
ans=1;
while(n>1)
{
n>>=1;
ans<<=1;
}
ans--;
printf("%d\n",ans);
}
}
给定一个长度为 n ( 1 ≤ n ≤ 1 0 3 ) n(1 \leq n \leq 10^3) n(1≤n≤103) 的 01 01 01 字符串,Alice和Bob双方论轮流修改,每次修改可以进行以下操作之一:
当所有字符都变为 1 1 1 是,游戏结束。花费最少的玩家获得胜利。双方都会采用最优策略,Alice先手行动。给定字符串,求Alice胜,还是Bob胜,或是平手。
本题有两个版本。Easy版本中给定字符串为回文串,Hard版本中给定字符串不一定是回文串。
本题有两种解法,一种是直接通过贪心策略手工推导结论,另一种是用博弈论思想跑动态规划。
设 d p s a m e , d i f f , m i d , r e v dp_{same,diff,mid,rev} dpsame,diff,mid,rev 表示在双方选择最优策略的情况下,局面为 ( s a m e , d i f f , m i d , r e v ) (same,diff,mid,rev) (same,diff,mid,rev) 时当前选手与对手的花费差,其中:
那么当前可执行的操作及转移式分别为:
找出上述符合条件的最小值,即为当前的 d p s a m e , d i f f , m i d , r e v dp_{same,diff,mid,rev} dpsame,diff,mid,rev 值。
若初始状态的 d p s a m e , d i f f , m i d , r e v dp_{same,diff,mid,rev} dpsame,diff,mid,rev 值 > 0 >0 >0 则表示先手Alice花费更多,Bob胜; < 0 <0 <0 则Alice胜; = 0 =0 =0 则平手。
#include
using namespace std;
const int MAXN=1000;
int dp[MAXN>>1][MAXN>>1][2][2],vis[MAXN>>1][MAXN>>1][2][2];
char s[MAXN];
int dfs(int sam,int dif,int mid,int rev)
{
if(vis[sam][dif][mid][rev])
return dp[sam][dif][mid][rev];
int ret=1<<29;
if(dif&&rev==0)
ret=min(ret,-dfs(sam,dif,mid,1));
if(sam)
ret=min(ret,1-dfs(sam-1,dif+1,mid,0));
if(mid)
ret=min(ret,1-dfs(sam,dif,0,0));
if(dif)
ret=min(ret,1-dfs(sam,dif-1,mid,0));
vis[sam][dif][mid][rev]=1;
return dp[sam][dif][mid][rev]=ret;
}
int main()
{
int T,n,sam,dif,mid,i,ans;
dp[0][0][0][0]=0;
vis[0][0][0][0]=1;
dp[0][0][0][1]=0;
vis[0][0][0][1]=1;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
scanf("%s",&s);
sam=dif=0;
if(n%2==1&&s[n/2]=='0')
mid=1;
else
mid=0;
for(i=0;i<=n/2-1;i++)
{
if(s[i]==s[n-1-i]&&s[i]=='0')
sam++;
else if(s[i]=='0'||s[n-1-i]=='0')
dif++;
}
ans=dfs(sam,dif,mid,0);
if(ans>0)
printf("BOB\n");
else if(ans==0)
printf("DRAW\n");
else
printf("ALICE\n");
}
}
对于Easy版本,即输入字符串为回文串,有以下结论:
对于Hard版本,即输入字符串不一定回文串,有以下结论:
定义一个序列的权值,为这个序列中满足 $i < j $ 且 a i = a j a_i=a_j ai=aj 的 ( i , j ) (i,j) (i,j) 对数。
给定一个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105) 的序列 a a a ,求序列 a a a 的所有子序列的权值和。
如果可以通过在序列 a a a 的开始与结尾处中删除若干个元素得到序列 b b b ,则称序列 b b b 是序列 a a a 的子序列。
考虑如果存在一队 ( i , j ) (i,j) (i,j) ,满足 $i < j $ 且 a i = a j a_i=a_j ai=aj ,那么有多少子序列包含这对 ( i , j ) (i,j) (i,j) ,就是其对答案产生的贡献。
设子序列开始与结束位置分别为 l l l 与 r r r ,易得当 1 ≤ l ≤ r 1 \leq l \leq r 1≤l≤r 且 j ≤ r ≤ n j \leq r \leq n j≤r≤n 时,子序列 a [ l , r ] a[l,r] a[l,r] 包含点对 ( i , j ) (i,j) (i,j) ,这样的子序列有 i ∗ ( n − j + 1 ) i*(n-j+1) i∗(n−j+1) 个。
因此我们可以从左到右扫描一遍序列,并统计更新每种元素各自的下标值和 ∑ i \sum{i} ∑i , 乘上 ( n − j + 1 ) (n-j+1) (n−j+1) 计入答案即可。具体的计算式为:
a n s = ∑ j = 1 n ( ( n − j + 1 ) ∗ ∑ i < j & a i = a j i ) ans=\sum_{j=1}^{n} ((n-j+1) * \sum_{i
另外由于 a i a_i ai 很大,因此统计前需要离散化或者直接用map。
#include
using namespace std;
const int MAXN=100100;
int a[MAXN],cp[MAXN];
long long num[MAXN];
map<int,int> mp;
int main()
{
int T,n,i,len;
long long ans;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
cp[i]=a[i];
}
sort(cp+1,cp+n+1);
len=unique(cp+1,cp+n+1)-cp-1;
mp.clear();
for(i=1;i<=len;i++)
{
mp[cp[i]]=i;
num[i]=0;
}
ans=0;
for(i=1;i<=n;i++)
{
a[i]=mp[a[i]];
ans+=(n-i+1)*num[a[i]];
num[a[i]]+=i;
}
printf("%lld\n",ans);
}
}
给定一棵具有 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \leq n \leq 2 \cdot 10^5) n(2≤n≤2⋅105) 个节点的树,节点编号从 0 0 0 到 n − 1 n-1 n−1 。
对于 [ 0 , n ] [0,n] [0,n] 范围内的每一个整数 k k k ,求有多少个无序点对 ( u , v ) , u ≠ v (u,v),u \neq v (u,v),u=v ,满足从 u u u 到 v v v 路径上所有节点的 M E X MEX MEX 值为 k k k。
一个序列的 M E X MEX MEX 值为不属于这个序列的最小非负整数。
如果要使得一条路径上节点的 M E X MEX MEX 值为 k k k ,则路径上必须包含 [ 0 , k − 1 ] [0, k-1] [0,k−1] 范围内的所有节点且不包含 k k k 。
直接求解的话会发现不包含 k k k 这个条件比较难解决,但是可以考虑用差分的方法解决:
那么只要求解出对于任意 k ( 0 ≤ k ≤ n ) k(0 \leq k \leq n) k(0≤k≤n) ,包含 [ 0 , k ] [0,k] [0,k] 的路径数即可。
考虑维护这样的路径。
一开始时,求包含 [ 0 , 0 ] [0,0] [0,0] 的路径,不妨将 0 0 0 当作根节点跑一遍dfs,统计每个节点为根的子树大小,那么经过 0 0 0 的路径数可以直接统计出。
接下来考虑包含 [ 0 , 1 ] [0,1] [0,1] 的路径,如下图所示,等于红色节点数乘蓝色节点数。
接下来考虑包含 [ 0 , 2 ] [0,2] [0,2] 的路径。这时有以下几种可能性:
接下来考虑包含 [ 0 , 3 ] [0,3] [0,3] 的路径,这时候多了四种情况:
对于后续节点,也是如上操作。最后输出答案前差分下结果即可。
#include
using namespace std;
typedef long long ll;
const int MAXN=200200;
vector<int> vec[MAXN];
int cnt;
int dfn[MAXN],ed[MAXN];
long long siz[MAXN],ans[MAXN];
void dfs(int x,int fa)
{
siz[x]=1;
dfn[x]=++cnt;
for(int i=0;i<vec[x].size();i++)
{
int son=vec[x][i];
if(son==fa)
continue;
dfs(son,x);
siz[x]+=siz[son];
}
ed[x]=cnt;
}
bool isParent(int x,int fa)
{
return dfn[fa]<=dfn[x]&&dfn[x]<=ed[fa];
}
int main()
{
int T,n,i,u,v,son,x,A,B,flag;
long long all;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=0;i<n;i++)
vec[i].clear();
for(i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
cnt=0;
dfs(0,-1);
all=1ll*n*(n-1)/2;
ans[0]=0;
for(i=0;i<vec[0].size();i++)
{
son=vec[0][i];
ans[0]+=siz[son]*(siz[son]-1)/2;
if(isParent(1,son))
x=son;
}
A=1;
B=0;
flag=1;
ans[1]=all-siz[A]*(n-siz[x]);
for(i=2;i<n;i++)
{
ans[i]=all;
if(!flag)
continue;
if(isParent(i,A))
A=i;
else if(!isParent(A,i))
{
if(!B)
{
if(isParent(i,x))
flag=0;
else
B=i;
}
else
{
if(isParent(i,B))
B=i;
else if(!isParent(B,i))
flag=0;
}
}
if(flag)
{
if(!B)
ans[i]-=siz[A]*(n-siz[x]);
else
ans[i]-=siz[A]*siz[B];
}
}
ans[n]=all;
for(i=n;i>=1;i--)
ans[i]-=ans[i-1];
for(i=0;i<=n;i++)
printf("%lld ",ans[i]);
puts("");
}
}
定义数组 t t t 的花费为:
c o s t ( t ) = ∑ x ∈ s e t ( t ) l a s t ( x ) − f i r s t ( x ) cost(t)=\sum_{x \in set(t)}{last(x)-first(x)} cost(t)=x∈set(t)∑last(x)−first(x)
其中 s e t ( t ) set(t) set(t) 为 t t t 中所有值去重后构成的集合, f i r s t ( x ) first(x) first(x) 和 l a s t ( x ) last(x) last(x) 为 t t t 中第一次和最后一次出现 x x x 的位置。
给定长度为 n ( 1 ≤ n ≤ 35000 ) n(1 \leq n \leq 35000) n(1≤n≤35000) 的数组 a a a ,将其分为 m ( 1 ≤ m ≤ m i n ( n , 100 ) ) m(1 \leq m \leq min(n,100)) m(1≤m≤min(n,100)) 个连续段,求每段花费之和的最小值。
设 d p i , j dp_{i,j} dpi,j 表示前 i i i 个数分为 j j j 段的最小花费,易得
d p i , j = m i n k < i { d p k , j − 1 + c o s t ( k + 1 , i ) } dp_{i,j}=min_{kdpi,j=mink<i{ dpk,j−1+cost(k+1,i)}
直接求解的复杂度是 O ( N 2 k ) O(N^2k) O(N2k) ,超时,考虑数据结构优化。
考虑比较 i i i 增大时 c o s t cost cost 的变化。对比 c o s t ( k + 1 , i − 1 ) cost(k+1,i-1) cost(k+1,i−1) 和 c o s t ( k + 1 , i ) cost(k+1,i) cost(k+1,i) ,可能有以下变化:
因此可以采用线段树维护 d p k , j − 1 + c o s t ( k + 1 , i ) dp_{k,j-1} +cost(k+1,i) dpk,j−1+cost(k+1,i) ,此时动规的循环为 j j j 循环在外, i i i 循环在内。每当 i i i 增大时,将 [ 1 , b e f a i − 1 ] [1,bef_{a_i}-1] [1,befai−1] 范围内的点增加 i − b e f a i i-bef_{a_i} i−befai 即可维护 d p k , j − 1 + c o s t ( k + 1 , i ) dp_{k,j-1} +cost(k+1,i) dpk,j−1+cost(k+1,i) 。
#include
#define ll long long
using namespace std;
ll n,m,i,j,k,l,o,p,dp[35010][105],a[35010],b[35010];
struct node
{
ll l,r,sum,inc;
}tr[35010<<2];
void build(ll i,ll l,ll r)
{
tr[i].l=l;tr[i].r=r;tr[i].inc=0;
if (l==r)
{
tr[i].sum=dp[l][j-1];
return;
}
ll mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
tr[i].sum=min(tr[i<<1].sum,tr[i<<1|1].sum);
}
ll query(ll i,ll l,ll r)
{
if (tr[i].l==l && tr[i].r==r)
{
return tr[i].sum;
}
if (tr[i].inc)
{
tr[i<<1].inc+=tr[i].inc;
tr[i<<1].sum+=tr[i].inc;
tr[i<<1|1].inc+=tr[i].inc;
tr[i<<1|1].sum+=tr[i].inc;
tr[i].inc=0;
}
ll mid=(tr[i].l+tr[i].r)>>1;
if (r<=mid)
return query(i<<1,l,r);
else if (l>mid)
return query(i<<1|1,l,r);
else
return min(query(i<<1,l,mid),query(i<<1|1,mid+1,r));
}
void add(ll i,ll l,ll r,ll k)
{
if (tr[i].l==l && tr[i].r==r)
{
tr[i].inc+=k;
tr[i].sum+=k;
return;
}
if (tr[i].inc)
{
tr[i<<1].inc+=tr[i].inc;
tr[i<<1].sum+=tr[i].inc;
tr[i<<1|1].inc+=tr[i].inc;
tr[i<<1|1].sum+=tr[i].inc;
tr[i].inc=0;
}
ll mid=(tr[i].l+tr[i].r)>>1;
if (r<=mid)
add(i<<1,l,r,k);
else if (l>mid)
add(i<<1|1,l,r,k);
else
{
add(i<<1,l,mid,k);
add(i<<1|1,mid+1,r,k);
}
tr[i].sum=min(tr[i<<1].sum,tr[i<<1|1].sum);
}
int main()
{
scanf("%lld%lld",&n,&m);
for (i=1;i<=n;i++)
scanf("%lld",&a[i]);
for (i=1;i<=n;i++)
dp[i][1]=0;
for (i=1;i<=n;i++)
{
dp[i][1]=dp[i-1][1];
if (b[a[i]]!=0)
dp[i][1]+=i-b[a[i]];
b[a[i]]=i;
}
for (j=2;j<=m;j++)
{
build(1,1,n);
for (i=1;i<=n;i++)
{
if(b[a[i]]<i&&b[a[i]]>1)
add(1,1,b[a[i]]-1,i-b[a[i]]);
b[a[i]]=i;
if (i>=j)
dp[i][j]=query(1,1,i-1);
else
dp[i][j]=1ll<<60;
}
}
printf("%lld\n",dp[n][m]);
}