题意: 给一个n,求最小的k满足
n & (n−1) & (n−2) & (n−3) & … (k) = 0
思路:
因为是位运算,我们会发现在与的情况下,只需要出现一个0就可以消掉这一位上的数,我们会发现由于在数字不断减小的过程中,后面几位会不断地在0和1之间变换,那么最难被消除掉的位其实就是n最前面的1,然后我们会发现在某个数到形如1111的数过程中,0会不断地在出现过的位置上出现,所以,最后的答案就是小于n的最大的二进制形式为1111的数
代码:
#include
#include
#include
using namespace std;
int main()
{
int t,n;
cin>>t;
while(t--)
{
cin>>n;
if(n==1) printf("0\n");
else {
vector<int> res;
while(n>0)
{
res.push_back(n%2);
n/=2;
}
int num=(int)res.size()-1;
int ans=0;
for(int i=0;i<num;i++)
ans=ans*2+1;
printf("%d\n",ans);
}
}
return 0;
}
题意: 给一个回文串,先手和后手有两种操作:
1.把一个0,变成一个1,花费1的代价
2.如果前一次操作不是2操作并且当前串不是个回文串,把当前串反转,不花费代价
思路: 我们会发现:
#include
#include
#include
using namespace std;
int main()
{
int t,n;
char s[1010];
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
scanf("%s",s);
int num=0;
for(int i=0;i<n;i++)
if(s[i]=='0') num++;
if(num==1) printf("BOB\n");
else if(num%2==0) printf("BOB\n");
else printf("ALICE\n");
}
return 0;
}
题意: 跟B1一样,只不过给的串不一定是回文串了
思路: 是回文串的情况如B1,
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e5+100;
char s[1010];
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
scanf("%s",s);
int f=0;
int num0=0;
for(int i=0;i<n;i++)
{
if(s[i]=='0') num0++;
if(s[i]=='0'&&s[i]!=s[n-1-i]) f++;
}
if(f==1&&num0==2)
printf("DRAW\n");
else if(f>0)
{
printf("ALICE\n");
}
else
{
if(num0==1) printf("BOB\n");
else if(num0%2==0) printf("BOB\n");
else printf("ALICE\n");
}
}
return 0;
}
题意: 定义sum为一个数列中相同的数在不同位置的对数,问对于一个数列,它的串及其所有子串的sum和为多少
思路:
对于样例一,我们可以这样来考虑:
1 , 2 , 1 , 1
他们对应的位置是1 ,2 ,3 ,4
对于(1,3)这一对,会出现在【1,3】和【1,4】中,所以贡献为2(1 * 2 )
对于(1,4)这一对,会出现在【1,4】中,所以贡献为1(1 * 1)
对于(3,4)这一对,会出现在【1,4】【2,4】【3,4】中,所以贡献为3(1 * 3)
然后我们会发现每一对的贡献其实就是(左边的个数)*(右边的个数)
这样显然复杂度是O(n^2)的,我们可以把左端点统一一下,把右端点的答案用一个类似后缀和的数组预处理一下,就是最后的答案
代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e5+100;
int a[maxn],b[maxn];
int k;
ll hou[maxn];
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++){scanf("%d",&a[i]);b[i]=a[i];hou[i]=0;}
sort(b,b+n);
int k=unique(b,b+n)-b;
vector<int> qaq[k+1];
for(int i=0;i<n;i++)
{
a[i]=lower_bound(b,b+k,a[i])-b;
qaq[a[i]].push_back(i);
}
for(int i=0;i<k;i++)
{
int num=(int)qaq[i].size();
hou[qaq[i][num-1]]=0;
for(int j=num-2;j>=0;j--)
{
hou[qaq[i][j]]=hou[qaq[i][j+1]]+n-qaq[i][j+1];
}
}
ll ans=0;
for(int i=0;i<n;i++)
{
ans+=(i+1)*hou[i];
}
cout<<ans<<endl;
}
return 0;
}
题意: mex的定义:对于一个集合中的树,最小的没有出现过的非负整数
求一棵树上mex=0,1,2,…,n的简单路径各有多少条
思路: 首先显然我们会发现
第一个结论:
mex=0 | 无0 | |
---|---|---|
mex=1 | 有0 | 无1 |
mex=2 | 有0 ,1 | 无2 |
mex=3 | 有0 ,1,2 | 无3 |
这样显然我们会发现
(mex=k的路径条数)=(有[0,k-1]的路径条数)-(有[0,k]的路径条数)
第二个结论:
如果有[0,k]的路径存在,那么[0,k]这些数字必然会在一条链上
代码:
#include
#include
#include
#include
#include
#include
#define ls k<<1
#define rs k<<1|1
using namespace std;
typedef long long ll;
const int maxn=2e5+100;
int t,n;
vector<int> G[maxn];
int fa[maxn],siz[maxn],vis[maxn];
ll ans[maxn];
int st,en;
void dfs(int x,int f)
{
siz[x]=1;
fa[x]=f;
for(int i=0;i<G[x].size();i++)
{
int to=G[x][i];
if(to==f) continue;
dfs(to,x);
siz[x]+=siz[to];
}
}
ll cal()
{
if(st==en)
{
ll ans=0;
for(int i=0;i<G[st].size();i++)
{
int to=G[st][i];
ans+=siz[to]*2+1ll*(siz[to])*(n-1-siz[to]);//子树到根和子树到其他子树
}
return ans/2;
}
return 1ll*siz[st]*siz[en];
}
int add(int x)
{
if(vis[x]) return 1;
int which=x;
int sum=siz[x];
while(vis[which]==0)
{
vis[which]=1;
siz[fa[which]]-=sum;
sum+=siz[fa[which]];
which=fa[which];
}
//如果在两头,直接加上
if(which==st)
{
st=x;
return 1;
}
if(which==en)
{
en=x;
return 1;
}
//如果不在链上并且不在不是接在两端
return 0;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<=n+1;i++)
{
G[i].clear();
fa[i]=-1;
vis[i]=0;
siz[i]=0;
ans[i]=0;
}
for(int i=0,u,v;i<n-1;i++)
{
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(0,-1);
st=en=0;
ans[0]=1ll*n*(n-1)/2;
vis[0]=1;
vis[n]=1;
for(int i=1;i<=n;i++)
{
ans[i]=cal();
if(!add(i)) break;
}
for(int i=0;i<=n;i++)
printf("%lld ",ans[i]-ans[i+1]);
printf("\n");
}
return 0;
}
题意:
把一个长度为n的串分成k个片段,每个片段的贡献为下式
求最小的贡献
思路:
显然我们可以得到一个状态转移方程
d p [ i ] [ j ] = m i n ( d p [ k ] [ j − 1 ] + c o s t [ k + 1 ] [ i ] ) ( 其 中 k < i ) dp[i][j]=min(dp[k][j-1]+cost[k+1][i]) (其中kdp[i][j]=min(dp[k][j−1]+cost[k+1][i])(其中k<i)
但是这样的复杂度显然为O(k*n^2)
那么就考虑如何优化:
显然我们是要优化cost计算的过程,那么我们会发现
cost[i][j+1]可以从cost[i][j]得到:
那么显然,我们可以根据用i来转移,我们直接用线段树来维护dp[k][j-1]+cost[k+1][i],先把dp[k][j-1]都放上去,然后一步步向上加cost[k+1][i]
代码:
#include
#include
#include
#include
#include
#include
#define ls k<<1
#define rs k<<1|1
using namespace std;
typedef long long ll;
const int maxn=2e5+100;
const ll inf=1ll<<60;
int n,m;
int a[maxn];
ll dp[maxn][110];
int bef[maxn];
int j;
struct
{
int l,r;
ll minn;
ll lazy;
}node[maxn<<2];
void up(int k)
{
node[k].minn=min(node[ls].minn,node[rs].minn);
}
void build(int k,int l,int r)
{
node[k].l=l;node[k].r=r;
node[k].lazy=0;
if(l==r)
{
node[k].minn=dp[l][j-1];
return ;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
up(k);
}
void push(int k)
{
if(node[k].l==node[k].r) return ;
if(node[k].lazy!=0)
{
node[ls].minn+=node[k].lazy;
node[rs].minn+=node[k].lazy;
node[ls].lazy+=node[k].lazy;
node[rs].lazy+=node[k].lazy;
node[k].lazy=0;
}
}
ll query(int k,int l,int r)
{
if(l<=node[k].l&&node[k].r<=r)
return node[k].minn;
push(k);
int mid=node[k].l+node[k].r>>1;
ll minn=inf;
if(l<=mid) minn=min(minn,query(ls,l,r));
if(mid+1<=r) minn=min(minn,query(rs,l,r));
return minn;
}
void add(int k,int l,int r,int val)
{
if(l<=node[k].l&&node[k].r<=r)
{
node[k].minn+=val;
node[k].lazy+=val;
return ;
}
push(k);
int mid=node[k].l+node[k].r>>1;
if(l<=mid) add(ls,l,r,val);
if(mid+1<=r) add(rs,l,r,val);
up(k);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
dp[i][1]+=dp[i-1][1];
if(bef[a[i]]!=0)
dp[i][1]+=i-bef[a[i]];
bef[a[i]]=i;
}
for(j=2;j<=m;j++)
{
build(1,1,n);
for(int i=1;i<=n;i++)
{
if(bef[a[i]]>1&&bef[a[i]]<i)
add(1,1,bef[a[i]]-1,i-bef[a[i]]);
if(i>=j)
dp[i][j]=query(1,1,i-1);
else dp[i][j]=inf;
bef[a[i]]=i;
}
}
printf("%lld\n",dp[n][m]);
return 0;
}