A.直角三棱锥
在三维空间中,平面 x = 0, y = 0, z = 0,以及平面 x + y + z = K 围成了一个三棱锥。整天与整数打交道的小明希望知道这个三棱锥内、上整点的数目。他觉得数量可能很多,所以答案需要对给定的 M 取模。
答案为。注意mod的时候由于M不一定是质数,所以不能取逆元去做。
#include"stdio.h" long long a,b,c; void pan(){ long long i=0,j=0; if (a%2==0&&i==0) a=a/2,i=1; if (a%3==0&&j==0) a=a/3,j=1; if (b%2==0&&i==0) b=b/2,i=1; if (b%3==0&&j==0) b=b/3,j=1; if (c%2==0&&i==0) c=c/2,i=1; if (c%3==0&&j==0) c=c/3,j=1; } int main() { long long i,j,k,n,t; scanf("%lld",&t); for (i=1;i<=t;i++) { scanf("%lld %lld",&n,&k); a=n+1;b=n+2;c=n+3; pan(); long long ans=a%k*b%k*c%k; printf("%lld ",ans); } }
B.前缀查询
有一个村子,和村民。维护下列四种操作:
1.插入新人名 si,声望为 ai
2.给定名字前缀 pi 的所有人的声望值变化 di
3.查询名字为 sj 村民们的声望值的和(因为会有重名的)
4.查询名字前缀为 pj 的声望值的和
很明显的字典树了。别忘记每次pushdown的时候维护一下lazy标记。
/* 指针占内存大 所以要用数组字典树 trie[maxn][27];表示下一个字母的位置 sign[maxn]表示是否重复,val[maxn]表示前缀和 */ #include"stdio.h" #include"string.h" #define maxn 1050005 long long trie[maxn][27]; long long number[maxn];//人数 long long all[maxn];//声望和 long long name[maxn];//当前人的声望和 long long numname[maxn]; long long lazy[maxn];//标记 long long sum; char s[1000005]; void pushdown(long long p){ long long j; if (lazy[p]==0) return; for (j=1;j<=26;j++) if (trie[p][j]!=0){ long long son=trie[p][j]; lazy[son]+=lazy[p]; all[son]+=number[son]*lazy[p]; name[son]+=numname[son]*lazy[p]; } lazy[p]=0; } void createtrie(char s[],long long x){ long long i,l,j; long long p=0; long long id; for (i=0;i
C.可达性
给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。
强连通缩点之后,暴力判断某个回路的度。如果没有入度,那么在这个联通块上找一个最小的(以保证字典序最小)。
给出一组不缩点bug的数据。
4 4
2 1
2 3
3 4
4 2#include"stdio.h" #include"memory.h" struct node { int u,v,next; }edge[100050]; int dfn[100050],low[100050],belong[100050]; int stack[100050],head[100050],visit[100050],bcnt,cnt,tot,inde; int a[100050]; int in[100050]; void add(int x,int y){ cnt++; edge[cnt].u=x; edge[cnt].next=head[x]; edge[cnt].v=y; head[x]=cnt; } int min(int a,int b){if (a>b) return b;else return a;} void tarjan(int x){ int i; tot++; dfn[x]=tot; low[x]=tot; inde++; stack[inde]=x; visit[x]=1; for (i=head[x];i!=-1;i=edge[i].next){ if (dfn[edge[i].v]==0){ tarjan(edge[i].v); low[x]=min(low[x],low[edge[i].v]); } else if (visit[edge[i].v]!=0){ low[x]=min(low[x],dfn[edge[i].v]); } } if (low[x]==dfn[x]){ bcnt++; do { // printf("%d",stack[inde]); visit[stack[inde]]=0; belong[stack[inde]]=bcnt; inde--; } while (x!=stack[inde+1]); } // printf(" "); return; } int main(){ int n,m; int x,y; int i; int sum=0; scanf("%d %d",&n,&m); memset(head,-1,sizeof(head)); memset(visit,0,sizeof(visit)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); cnt=0;tot=0;inde=0;bcnt=0;sum=0; for(i=1;i<=m;i++) { scanf("%d %d",&x,&y); add(x,y); } for(i=1;i<=n;i++) if(dfn[i]==0) tarjan(i); memset(in,0,sizeof(in)); for (i=1;i<=cnt;i++) if (belong[edge[i].v]!=belong[edge[i].u]) in[belong[edge[i].v]]=1; int l; l=0; for (i=1;i<=n;i++) if (in[belong[i]]==0) { l++; a[l]=i; in[belong[i]]=1; } printf("%d ",l); for (i=1;i
D.codeJan和树
给定一棵树,以1为根,树上的每条边都有距离。记每个点的值为从这个点出发,到所有在以这个点为根的子树中的点的距离之和。现在,给定一个m,你需要在树上找一个点,使得这个点的值减去以它为根的子树中的点的值<=m的最大值是多少。
通过一遍树上dp,我们很容易求得每个点的值。然后,陷入沉思。如果对每个点都进行询问,然后去遍历它的子结点,显然,复杂度为O(n^2)。
显然,这样的复杂度是完全不行的。那么,我们尝试使用数据结构去降低一下查询的复杂度。由于针对每一次查询的时候,
sum[k]-sum[son]<=m
中的sum[k]已经知道,我们需要求得最小的son,使得sum[k]-m<=sum[son]
。在这里,我们可以应用一下划分树。也就是说,我们先用dfs序构造出来,然后放到划分树里。每次枚举结点的时候,可以找到对应的子树区间。然后对于子树区间,二分枚举第k大,然后划分树log找到值,去判断合不合法。复杂度约为O(n*log(n)^2)。显然,这么麻烦的方法,我根本懒得去实现。于是,看了题解。下面是题解介绍的方法,代码量短,思路简单,1A。
在我们树上dp之后,可以发现,从每个结点,一直往上找,找到父亲结点,他们的值是递增的(这tm的不是废话)。所以,我们可以针对每一个结点,去询问它的所有父辈祖辈祖祖辈辈。倍增的方法,二分即可。复杂度为O(n*log(n))。
#include"stdio.h" #include"string.h" #define maxn 105000 struct node{ long long v,dis,next; }edge[2*maxn]; long long head[maxn]; long long sum[maxn]; long long number[maxn]; long long sign[maxn][30]; long long cnt; long long m; long long ans; long long max(long long a,long long b){ if (a>b) return a;return b; } void add(long long u,long long v,long long dis){ cnt++; edge[cnt].v=v; edge[cnt].dis=dis; edge[cnt].next=head[u]; head[u]=cnt; } void dfs(long long k,long long father){ long long i,v; number[k]=1; sum[k]=0; for (i=head[k];i!=-1;i=edge[i].next){ v=edge[i].v; if (v!=father){ dfs(v,k); number[k]+=number[v]; sum[k]+=sum[v]+number[v]*edge[i].dis; } } } void find(long long k,long long father){ long long i,v; long long now,ff; for (i=head[k];i!=-1;i=edge[i].next){ v=edge[i].v; if (v!=father) { sign[v][0]=v; now=0;ff=k; while(ff!=-1) { now++; sign[v][now]=sign[ff][now-1]; ff=sign[ff][now-1]; } find(v,k); } } } void query(long long k,long long father) { long long i,v; for (i=head[k];i!=-1;i=edge[i].next) { v=edge[i].v; if (v!=father){ query(v,k); } } long long now=0; long long ff=k; int j=1; while(sign[ff][j]!=-1) { now=sign[ff][j]; if (sum[now]-sum[k]<=m) ans=max(ans,sum[now]-sum[k]); j++; now=sign[ff][j]; if (sum[now]-sum[k]>m) { ff=sign[ff][j-1]; j=1; now=sign[ff][j]; if (now==-1||sum[now]-sum[k]>m) break; } } } int main(){ long long e,t,n,x,y,dis; long long i,j; scanf("%lld",&t); for (e=1;e<=t;e++) { cnt=0; memset(head,-1,sizeof(head)); scanf("%lld %lld",&n,&m); for (i=1;i
E.无效位置
给一个1-base数组{a},有N次操作,每次操作会使一个位置无效。一个区间的权值定义为这个区间里选出一些数的异或和的最大值。求在每次操作前,所有不包含无效位置的区间的权值的最大值。
只要知道线性基这个东西,就应该能写过此题吧。
线性基大概就是,像网络流类比水流一样,可以把异或中的线性基类比为n维向量空间的一组基。n维向量空间中,可以找到一族基向量,比如单位基,可以去表示空间中的所有向量。我们可以发现,如果我们在异或基中,取2进制的1,10,100,1000,10000......这样子取,空间中的任何数字都能被这组基所表示出来。当然,n为向量空间中的一组基不一定要是单位基,只要n个向量线性无关即可。所以,我们在取线性基的时候,同样保证线性无关即可,即基中的任意一个数不能通过其他的数异或得到。
由于数字最大为1e9,不超过32位整数。所以最多会产生32个基,于是我们只需要开[1e5*32]的空间即可。然后,倒着开始做。每次用并查集来维护区间,将这个点和左右区间相连。然后线性基暴力合并,查找一下最大值。
#include"stdio.h" #include"string.h" #include"algorithm" using namespace std; long long sign[105000][40]; long long b[105000]; long long vis[105000]; long long a[105000]; long long wei[105000]; long long father[105000]; void insert(long long k,long long wei){ long long i; for (i=40;i>=1;i--) if ((k&(1LL<<(i-1)))>0){ if (sign[wei][i]>0) k=k^sign[wei][i]; else { sign[wei][i]=k; return; } } } long long getfather(long long k){ if (father[k]==k) return k; else father[k]=getfather(father[k]); return father[k]; } long long find(long long wei){ long long i; long long ans=0; for (i=40;i>=1;i--)if (sign[wei][i]>0) if ((ans^sign[wei][i])>ans) ans=(ans^sign[wei][i]); return ans; } int main(){ long long ans,n,i,j,x,y; long long l=0; scanf("%lld",&n); for (i=1;i<=n;i++) {scanf("%lld",&a[i]);insert(a[i],i);} for (i=1;i<=n;i++) scanf("%lld",&wei[i]); for (i=1;i<=n;i++) father[i]=i; memset(vis,0,sizeof(vis)); ans=0; for (i=n;i>=1;i--){ if (vis[wei[i]-1]==1) { x=getfather(wei[i]-1); y=getfather(wei[i]); father[y]=x; for (j=1;j<=40;j++) if (sign[y][j]>0){ insert(sign[y][j],x); } } if (vis[wei[i]+1]==1) { x=getfather(wei[i]); y=getfather(wei[i]+1); father[y]=x; for (j=1;j<=40;j++) if (sign[y][j]>0){ insert(sign[y][j],x); } } x=getfather(wei[i]); ans=max(find(x),ans); l++; b[l]=ans; vis[wei[i]]=1; } for (i=l;i>=1;i--) printf("%lld ",b[i]); }