题意:
在一棵树上,多个节点有人。选择一个节点使得这些人到这个点的路径最大值最小。
解析:
将不存在人的叶子都割掉,直到所有叶子都是人为止,你会发现就是求剩下来树的直径。
代码:
#include
using namespace std;
const int N=1e5+5;
struct node
{
int to,next;
}e[N*2];
int cnt,head[N];
void add(int x,int y)
{
e[cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt++;
}
int is[N],mx,p,ans;
void dfs(int x,int fa,int dis)
{
if(dis>mx&&is[x])
mx=dis,p=x;
for(int i=head[x];~i;i=e[i].next)
{
int ne=e[i].to;
if(ne==fa)
continue;
dfs(ne,x,dis+1);
}
}
int main()
{
memset(head,-1,sizeof(head));
int n,k,x,y;
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++)
scanf("%d%d",&x,&y),add(x,y),add(y,x);
for(int i=1;i<=k;i++)
scanf("%d",&x),is[x]=1;
p=x,mx=0;
dfs(x,0,0);
dfs(p,0,0);
printf("%d\n",(mx+1)/2);
}
题意:
给出n个集合,m个查询,每个查询 ( L , R , v a l ) (L,R,val) (L,R,val),询问区间 [ L , R ] [L,R] [L,R]中的所有集合是否可以通过集合内异或来表示 v a l val val。
解析:
异或表示显然就是线性基了,一个线性基是否可以表示一个数大家都会做,现在要求区间内所有线性基是否可以。
先来说一个很显然的东西,我们定义两个线性基的交为:两个基都可以表示的位。
我在之前讲过,线性基里面的每个基相当于二进制中的一个比特位,假设我第一个线性基可以表示 30 , 28 , 26 , 20 30,28,26,20 30,28,26,20,第二个 29 , 28 , 20 , 19 29,28,20,19 29,28,20,19,那么两个线性基的交就是 28 , 20 28,20 28,20。
那么这道题显然考察的就是线性基的交,我们只需要求出一个区间内所有线性基的交,再用这个线性基去判断即可。如果交可以表示,那么显然区间内的所有线性基都可以表示了。
这个过程可以用线段树去维护,每个节点存对应区间的所有线性基的交。
代码:
#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
struct linear_Bace;
typedef linear_Bace LB;
const int maxn=50000+9;
const int Len=33;
struct linear_Bace{
int a[Len];
int& operator [](int idx){
return a[idx];
}
int operator [](int idx)const{
return a[idx];
}
void insert(int val){
for(int i=Len-1;i>=0;i--){
if((val>>i)&1){
if(!a[i]){
a[i]=val;break;
}
val^=a[i];
}
}
}
bool find(int val){
for(int i=Len-1;i>=0;i--){
if((val>>i)&1){
if(a[i]){
val^=a[i];
}
else
return 0;
}
}
return 1;
}
};
LB merge(LB a,LB b){
LB ans=a;
for(int i=Len-1;i>=0;i--){
if(b[i]==0)continue;
ans.insert(b[i]);
}
return ans;
}
LB intersect(LB a,LB b){
LB ans= {},c=b,d=b;
for(int i=0;i<Len;i++) {
int x=a[i];
if(!x)
continue;
int j=i;
int T=0;
for(; j>=0; --j){
if((x>>j)&1)
if(c[j]) {
x^=c[j];
T^=d[j];
} else
break;
}
if(!x)
ans[i]=T;
else {
c[j]=x;
d[j]=T;
}
}
return ans;
}
LB arr[maxn];
LB tr[maxn<<2];
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
void build(int rt,int l,int r){
if(l==r){
tr[rt]=arr[l];
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
tr[rt]=intersect(tr[ls],tr[rs]);
}
bool query(int rt,int l,int r,int L,int R,int val){
if(l>=L&&r<=R){
return tr[rt].find(val);
}
if(L<=mid)
if(!query(ls,l,mid,L,R,val))return 0;
if(R>mid)
if(!query(rs,mid+1,r,L,R,val))return 0;
return 1;
}
int main(){
int n,q;scanf("%d%d",&n,&q);
rep(i,1,n){
int k;scanf("%d",&k);
while(k--){
int val;scanf("%d",&val);
arr[i].insert(val);
}
}
build(1,1,n);
while(q--){
int l,r,val;scanf("%d%d%d",&l,&r,&val);
printf("%s\n",query(1,1,n,l,r,val)?"YES":"NO");
}
}
题意:
给出两个数组 a , b a,b a,b,区间 [ L , R ] [L,R] [L,R]值为 m i n ( a [ L , R ] ) ∗ s u m ( b [ L , R ] ) min(a_{[L,R]})*sum(b_{[L,R]}) min(a[L,R])∗sum(b[L,R]),求值的最大值。
解析:
枚举每个数作为最小值的区间,如果 a i a_i ai为为正数,区间和最大的为前缀和数组在右边的最大值减左边的最小值, a i a_i ai为负数时相反。
维护每个数的区间可以用单调栈或者笛卡尔树做。
前缀和区间最值用线段树维护。
代码:
#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define LL long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
const int maxn=3e6+5;
LL a[maxn];
LL b[maxn];
LL pre[maxn];
int _rt,_ls[maxn],_rs[maxn];
int top;
int sta[maxn];
bool vis[maxn];
LL ma[maxn<<2];
LL mi[maxn<<2];
void build(int rt,int l,int r){
if(l==r){
ma[rt]=mi[rt]=pre[l];
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
ma[rt]=max(ma[ls],ma[rs]);
mi[rt]=min(mi[ls],mi[rs]);
}
LL query_max(int rt,int l,int r,int L,int R){
if(l>=L&&r<=R){
return ma[rt];
}
LL ans=-1e18;
if(L<=mid)ans=query_max(ls,l,mid,L,R);
if(R>mid)ans=max(ans,query_max(rs,mid+1,r,L,R));
return ans;
}
LL query_min(int rt,int l,int r,int L,int R){
if(l>=L&&r<=R){
return mi[rt];
}
LL ans=1e18;
if(L<=mid)ans=query_min(ls,l,mid,L,R);
if(R>mid)ans=min(ans,query_min(rs,mid+1,r,L,R));
return ans;
}
void init(int n){
int tmp;
top=0;
rep(i,1,n){
vis[i]=0;
_ls[i]=_rs[i]=0;
}
rep(i,1,n){
tmp=top;
while(tmp&&a[sta[tmp-1]]>a[i])tmp--;
if(tmp)_rs[sta[tmp-1]]=i;
if(top>tmp)_ls[i]=sta[tmp];
sta[tmp++]=i;
top=tmp;
}
rep(i,1,n){
vis[_ls[i]]=vis[_rs[i]]=1;
}
rep(i,1,n){
if(!vis[i]){
_rt=i;break;
}
}
}
int n;
void dfs(int _rt,int l,int r,LL &ans){
if(l>=r)return;
if(a[_rt]==0){
ans=max(ans,0ll);
}
else if(a[_rt]>0){
LL Ma=query_max(1,1,n,_rt,r);
LL Mi;
if(_rt==1)Mi=0;
else if(l==1)Mi=min(0ll,query_min(1,1,n,1,_rt-1));
else Mi=query_min(1,1,n,l-1,_rt-1);
ans=max(ans,a[_rt]*(Ma-Mi));
}
else{
LL Mi=query_min(1,1,n,_rt,r);
LL Ma;
if(_rt==1)Ma=0;
else if(l==1)Ma=max(0ll,query_max(1,1,n,1,_rt-1));
else Ma=query_max(1,1,n,l-1,_rt-1);
ans=max(ans,a[_rt]*(Mi-Ma));
}
dfs(_ls[_rt],l,_rt-1,ans);
dfs(_rs[_rt],_rt+1,r,ans);
}
int main(){
scanf("%d",&n);
LL ans=-1e18;
rep(i,1,n){
scanf("%lld",&a[i]);
}
rep(i,1,n){
scanf("%lld",&b[i]);
ans=max(ans,a[i]*b[i]);
pre[i]=pre[i-1]+b[i];
}
init(n);
build(1,1,n);
dfs(_rt,1,n,ans);
printf("%lld\n",ans);
}
题意:
找多个3的倍数,使其位或后等于给出的数。要求个数最少。
解析:
看成异或做了好久。。。按二进制看,奇数位模3余1,偶数位模3余2,也就是说把二进制看成 1212121 1212121 1212121,自己选择方案使得选择的数所有位之和为3的倍数即可。
代码:
#include
using namespace std;
typedef long long ll;
vector<int> bla1,bla2;
vector<int> hav1,hav2;
int bit[70],ct;
ll n;
void deal(ll x){
ct=0;
ll tmp=x;
memset(bit,0,sizeof(bit));
while(tmp){
bit[ct]=tmp%2;
ct++;
tmp/=2;
}
}
void add(ll pos,int flag,ll &ans){
if(flag==1){
pos=hav1[pos];
ans+=(1ll<<pos);
}
else{
pos=hav2[pos];
ans+=(1ll<<pos);
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%lld",&n);
if(n%3==0){
printf("1 %lld\n",n);
continue;
}
hav1.clear(); hav2.clear();
deal(n);
for(int i=0;i<60;i++){
if(bit[i]){
if(i%2) hav2.push_back(i);
else hav1.push_back(i);
}
}
int h1=hav1.size(),h2=hav2.size();
int cha=max(h1,h2)-min(h1,h2),now=min(h1,h2);
int addh1=now,addh2=now;
if(h1>h2) addh1+=cha-cha%3;
else addh2+=cha-cha%3;
ll ans1=0,ans2=0;
ll p1,p2;
cha=cha%3;
if(h1>h2&&cha==1||h2>h1&&cha==2){
if(cha==1){
add(addh1,1,ans2);
if(hav2.size()) add(0,2,ans2);
else add(addh1-1,1,ans2),add(addh1-2,1,ans2);
}
else{
add(addh2,2,ans2); add(0,2,ans2);
add(addh2+1,2,ans2);
}
for(int i=0;i<addh1;i++){
add(i,1,ans1);
}
for(int i=0;i<addh2;i++){
add(i,2,ans1);
}
}
else{
if(cha==2){//差两个1
add(addh1,1,ans2);
add(addh1+1,1,ans2); add(0,1,ans2);
}
else{//差一个2
add(addh2,2,ans2);
if(hav1.size()) add(0,1,ans2);
else add(addh2-1,2,ans2),add(addh2-2,2,ans2);
}
for(int i=0;i<addh1;i++){
add(i,1,ans1);
}
for(int i=0;i<addh2;i++){
add(i,2,ans1);
}
}
printf("2 %lld %lld\n",ans1,ans2);
}
return 0;
}
题意:
给出一个串,找出一个最大的子串集合,使得集合内任意两个串不相同(翻转后也算相同: a b c = c b a abc=cba abc=cba)
解析:
尝试用后缀自动机来做(可以得出出现次数为 k k k的子串的数量)。观察一个串与它的翻转: a b a c , c a b a abac,caba abac,caba。
分析情况:
设 F ( s ) F(s) F(s)为子串 s s s在原串以及其翻转中出现的次数之和。
综上所述,只有回文串的情况下不是2倍。
两个串怎么一起跑后缀自动机?
我们可以构建一个新串: S + ′ ∗ ′ + S − 1 S+'*'+S^{-1} S+′∗′+S−1,这样,新多出的子串必须中间符号,我们可以直接算出这部分子串的数量为 ( L e n S + 1 ) 2 (Len_S+1)^2 (LenS+1)2。
回文串怎么处理?
显然,我们可以先加上 s s s为回文串的情况数,再除二进行计算。那么这个情况数是什么呢?
分析后得出为原串中本质不同的回文串个数。这个东西直接用回文自动机跑出来就行。
计算结果:
我们让后缀自动机跑出出现1次以上的子串数量 a n s ans ans,也就是本质不同的子串的数量。
此时会计入包含 ′ ∗ ′ '*' ′∗′的串,所以 a n s = a n s − ( L e n S + 1 ) 2 ans=ans-(Len_S+1)^2 ans=ans−(LenS+1)2。
然后我们加上本质不同的回文串的数量后再除以二就是答案。
代码:
#include
#define ll long long
#define PB push_back
#define fst first
#define sec second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define ms(a,x) memset(a,x,sizeof(a))
typedef long long LL;
#define pi pair < int ,int >
#define MP make_pair
using namespace std;
const double eps = 1E-8;
const int dx4[4]={1,0,0,-1};
const int dy4[4]={0,-1,1,0};
const int inf = 0x3f3f3f3f;
const int N = 4e5+1000;
char s[N];
int k,lens;
struct PAM
{
int fail,cnt,len;
int nxt[27];
}st[N];
char RS[N];
int n,now,sz;
void pam_init()
{
ms(st,0);
st[0].fail = st[1].fail = 1;
st[1].len = -1;
sz = 1;
}
void extend(int c,int pos)
{
int p = now;
while (s[pos-st[p].len-1]!=s[pos]) p = st[p].fail;
if (!st[p].nxt[c]){
int np=++sz,q=st[p].fail;
st[np].len=st[p].len+2;
while (s[pos-st[q].len-1]!=s[pos]) q=st[q].fail;
st[np].fail=st[q].nxt[c];
st[p].nxt[c] = np;
}
now=st[p].nxt[c];
st[now].cnt++;
}
struct SAM{
int last,cnt,nxt[N*2][27],fa[N*2];
ll l[N*2],num[N*2];
ll ans;
void init(){
last = cnt=1;
memset(nxt[1],0,sizeof nxt[1]);
fa[1]=l[1]=num[1]=0;
ans=0;
}
int inline newnode(){
cnt++;
memset(nxt[cnt],0,sizeof nxt[cnt]);
fa[cnt]=l[cnt]=num[cnt]=0;
return cnt;
}
void add(int c){
int p = last;
int np = newnode();
last = np;
l[np] =l[p]+1;
while (p&&!nxt[p][c]){
nxt[p][c] = np;
p = fa[p];
}
if (!p){
fa[np] =1;
}else{
int q = nxt[p][c];
if (l[q]==l[p]+1){
fa[np] =q;
}else{
int nq = newnode();
memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
fa[nq] =fa[q];
num[nq] = num[q];
l[nq] = l[p]+1;
fa[np] =fa[q] =nq;
while (nxt[p][c]==q){
nxt[p][c]=nq;
p = fa[p];
}
}
}
int temp = last;
while (temp){
if (num[temp]>=k){
break;
}
num[temp]++;
if (num[temp]==k){
ans+=l[temp]-l[fa[temp]];
}
temp = fa[temp];
}
}
}sam;
char ss[N];
int main()
{
scanf("%s",s);
int len=strlen(s);
pam_init();
vector <ll>aa;
for ( int i = 0 ; i < len; i++) extend(s[i]-'a',i),aa.PB(sz-1);
int siz = aa.size();
sam.init();
k=1;
for(int i=0;i<len;i++)
sam.add(s[i]-'a');
sam.add(26);
for(int i=len-1;i>=0;i--)
sam.add(s[i]-'a');
ll ans=sam.ans-(ll)(len+1)*(len+1);
printf("%lld\n",(ans+aa[siz-1])/2ll);
return 0;
}
题意:
给出一个图,你可以让你路径上 k k k条边的权值变为0,求从 s s s到 t t t的最小权值。
解析:
直接迪杰斯特拉就行。
代码:
#include
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn = 1005;
const int maxm=1005;
const int inf=(int)1e9;
int dp[maxn][maxn];
//走到点i用了j次机会的最短路
int cnt,head[maxn],n,m,S,T,k,now,vis[maxn];
struct node{
int to,next,w;
}e[maxn*2];
struct Node{
int dis,id,ti;
Node(int dis,int id,int ti):dis(dis),id(id),ti(ti){}
bool operator < (const Node &a)const{
if(dis==a.dis) return ti>a.ti;
return dis>a.dis;
}
};
void init(){
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v,int w){
e[cnt].to=v,e[cnt].w=w;
e[cnt].next=head[u],head[u]=cnt++;
}
void dij(int st){
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++) dp[i][j]=inf;
vis[i]=0;
}
for(int i=0;i<=k;i++) dp[st][i]=0;
priority_queue<Node> q;
q.push(Node(0,st,0));
while(!q.empty()){
int u=q.top().id; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(vis[v]) continue;
for(int j=0;j<=k;j++){
if(dp[v][j]>dp[u][j]+e[i].w){
dp[v][j]=dp[u][j]+e[i].w;
q.push(Node(dp[v][j],v,j));
}
if(j<k&&dp[v][j+1]>dp[u][j]){
dp[v][j+1]=dp[u][j];
q.push(Node(dp[v][j+1],v,j+1));
}
}
for(int j=1;j<=k;j++){
if(dp[v][j+1]>dp[v][j]) dp[v][j+1]=dp[v][j];
}
}
}
}
int main(){
init();
scanf("%d%d%d%d%d",&n,&m,&S,&T,&k);
rep(i,1,m){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
dij(S);
int ans=dp[T][0];
for(int i=1;i<=k;i++){
ans=min(ans,dp[T][i]);
}
printf("%d\n",ans);
return 0;
}
题意:
求可以被300整数的子串数量。
解析:
处理 0 0 0和 00 00 00的情况后,相当于找出前面有多少个被3整数的子串。显然状态只有余 0 , 1 , 2 0,1,2 0,1,2三种。
代码:
#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define LL long long
int main(){
string x;cin>>x;
int ct[3]={0,0,0};
LL ans=0;
rep(i,0,x.length()-1){
if(x[i]=='0'){
ans++;
if(i+1<x.length()&&x[i+1]=='0'){
ans++;
ans+=(LL)ct[0];
}
}
int add=(x[i]-'0')%3;
int tmp[3];
rep(j,0,2){
tmp[(j+add)%3]=ct[j];
}
tmp[(x[i]-'0')%3]++;
rep(i,0,2)ct[i]=tmp[i];
}
printf("%lld\n",ans);
}