A:考虑每个质因子,显然要求次数之和是3的倍数,且次数之差的两倍不小于较小的次数。对于第一个要求,乘起来看开三次方是否是整数即可。第二个取gcd,两个数分别除掉gcd,之后看两个数的剩余部分是否都能被gcd整除即可。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n; ll cube(ll x) { return x*x*x; } signed main() { n=read(); while (n--) { int x=read(),y=read(); ll z=1ll*x*y,t=pow(z,1.0/3); if (cube(t-1)==z||cube(t)==z||cube(t+1)==z) { ll u=gcd(x,y); x/=u,y/=u; //cout<<x<<' '<<y<<' '<<u<<endl; if (u%x==0&&u%y==0) printf("Yes\n"); else printf("No\n"); } else printf("No\n"); } return 0; //NOTICE LONG LONG!!!!! }
B:显然有f[i][j]表示前i位分成j段的最大价值。考虑套路,在每个数最后一次出现的位置标1。这样转移时要查的就是后缀和与dp值的和的最大值。容易发现线段树区间加查查最大值就完了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 35010 #define M 55 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,f[N][M],a[N],pre[N],p[N],L[N<<2],R[N<<2],tree[N<<2],lazy[N<<2]; void up(int k){tree[k]=max(tree[k<<1],tree[k<<1|1]);} void build(int k,int l,int r,int x) { L[k]=l,R[k]=r;lazy[k]=0; if (l==r) {tree[k]=f[l][x];return;} int mid=l+r>>1; build(k<<1,l,mid,x); build(k<<1|1,mid+1,r,x); up(k); } void update(int k,int x) { tree[k]+=x; lazy[k]+=x; } void down(int k) { update(k<<1,lazy[k]); update(k<<1|1,lazy[k]); lazy[k]=0; } void add(int k,int l,int r) { if (L[k]==l&&R[k]==r) {tree[k]++,lazy[k]++;return;} if (lazy[k]) down(k); int mid=L[k]+R[k]>>1; if (r<=mid) add(k<<1,l,r); else if (l>mid) add(k<<1|1,l,r); else add(k<<1,l,mid),add(k<<1|1,mid+1,r); up(k); } int query(int k,int l,int r) { if (L[k]==l&&R[k]==r) return tree[k]; if (lazy[k]) down(k); int mid=L[k]+R[k]>>1; if (r<=mid) return query(k<<1,l,r); else if (l>mid) return query(k<<1|1,l,r); else return max(query(k<<1,l,mid),query(k<<1|1,mid+1,r)); } signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif n=read(),m=read(); for (int i=1;i<=n;i++) { a[i]=read(); pre[i]=p[a[i]]; p[a[i]]=i; } memset(f,200,sizeof(f));f[0][0]=0; for (int j=1;j<=m;j++) { f[0][j]=0; build(1,0,n,j-1); for (int i=1;i<=n;i++) { add(1,pre[i],i-1); f[i][j]=query(1,0,i-1); } } cout<<f[n][m]; return 0; //NOTICE LONG LONG!!!!! }
C:注意到事实上答案并不会很大,于是暴力枚举每个尾巴判断是否合法即可。至于如何判断,逐位考虑,如果该位上下界相同就填上并继续考虑下一位,如果有可以既不卡上界又不卡下界的数显然就合法了,否则考虑是填上界还是下界,填完之后就只会有一个边界很好处理了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 20 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } ll l,r; int n,m,s,a[N],b[N],f[N],g[N],ans; bool calcup(int k) { for (int i=k;i>=0;i--) { if (i+1<s) return 0; for (int j=1;j<f[i];j++) if (a[j]) return 1; if (f[i]&&i+1>s) return 1; if (f[i]==0||a[f[i]]) { if (f[i]) a[f[i]]--,s--; } else return 0; } return s==0; } bool calcdown(int k) { for (int i=k;i>=0;i--) { if (i+1<s) return 0; for (int j=g[i]+1;j<10;j++) if (a[j]) return 1; if (g[i]==0||a[g[i]]) { if (g[i]) a[g[i]]--,s--; } else return 0; } return s==0; } void get() { ans++; //for (int i=1;i<=9;i++) cout<<b[i]<<' ';cout<<endl; } void check() { s=0; for (int i=1;i<=9;i++) s+=a[i]=b[i]; for (int i=n-1;i>=0;i--) { if (i+1<s) return; for (int j=g[i]+1;j<f[i];j++) if (a[j]) {get();return;} if (g[i]==f[i]) { if (f[i]) { if (!a[f[i]]) return; a[f[i]]--; s--; } } else { if (a[f[i]]) { int c[N];memcpy(c,a,sizeof(c));int tmps=s; a[f[i]]--;s--; if (calcup(i-1)) {get();return;} memcpy(a,c,sizeof(a));s=tmps; } if (a[g[i]]||g[i]==0) { if (g[i]) a[g[i]]--,s--; if (calcdown(i-1)) {get();return;} } return; } } if (s==0) get(); } void dfs(int k,int s) { if (k>9) {check();return;} for (int i=0;i<=18-s;i++) { b[k]=i; dfs(k+1,s+i); } } signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif cin>>l>>r; while (r) f[n++]=r%10,r/=10; while (l) g[m++]=l%10,l/=10; dfs(1,0); cout<<ans; return 0; //NOTICE LONG LONG!!!!! }
D:考虑点分。点分时考虑每条由根下去的链被统计了几次。化一下限制的式子容易发现是一个二维数点,bit套treap维护。常数小时限大二维数点O(nlog3n)就跑过去了。只是码力弱如我场上根本写不完。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<ctime> using namespace std; #define ll long long #define N 100010 #define P 1000000007 #define lson tree[k].ch[0] #define rson tree[k].ch[1] char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,p[N],len[N],size[N],t,ans=1,mx,mn; bool flag[N]; struct data{int to,nxt,len,color; }edge[N<<1]; struct data2{int x,y,x1,y1; }deep[N]; namespace bitreap { int root[N<<2],cnt=0; struct data{int ch[2],p,x,s;}tree[N<<5]; void up(int k){tree[k].s=tree[lson].s+tree[rson].s+1;} void move(int &k,int p) { int t=tree[k].ch[p]; tree[k].ch[p]=tree[t].ch[!p],tree[t].ch[!p]=k,up(k),up(t),k=t; } void ins(int &k,int x) { if (k==0) {k=++cnt;tree[k].p=rand();lson=rson=0;tree[k].x=x;tree[k].s=1;return;} tree[k].s++; if (tree[k].x<x) {ins(rson,x);if (tree[rson].p>tree[k].p) move(k,1);} else {ins(lson,x);if (tree[lson].p>tree[k].p) move(k,0);} } void del(int &k,int x) { if (tree[k].x==x) { if (lson==0||rson==0) {k=lson|rson;return;} if (tree[lson].p>tree[rson].p) move(k,0),del(rson,x); else move(k,1),del(lson,x); } else if (tree[k].x<x) del(rson,x); else del(lson,x); up(k); } int query(int k,int x) { if (k==0) return 0; if (tree[k].x<x) return query(rson,x); else return tree[rson].s+1+query(lson,x); } void Insert(int k,int x){k-=mn;k++;while (k<=mx-mn+1) ins(root[k],x),k+=k&-k;} void Delete(int k,int x){k-=mn;k++;while (k<=mx-mn+1) del(root[k],x),k+=k&-k;} int Query(int k,int x) { k-=mn;int s=0; while (k) s-=query(root[k],x),k^=k&-k; k=mx-mn+1; while (k) s+=query(root[k],x),k^=k&-k; return s; } } void addedge(int x,int y,int z,int c) { t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].len=z,edge[t].color=c,p[x]=t; } int ksm(int a,int k) { int s=1; for (;k;k>>=1,a=1ll*a*a%P) if (k&1) s=1ll*s*a%P; return s; } void makes(int k,int from) { size[k]=1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&!flag[edge[i].to]) { makes(edge[i].to,k); size[k]+=size[edge[i].to]; } } int findroot(int k,int s,int from) { int mx=0; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&!flag[edge[i].to]&&size[edge[i].to]>size[mx]) mx=edge[i].to; if ((size[mx]<<1)>s) return findroot(mx,s,k); else return k; } void make(int k,int from) { mx=max(mx,deep[k].x);mx=max(mx,deep[k].y); mn=min(mn,deep[k].x);mn=min(mn,deep[k].y); mx=max(mx,deep[k].x1);mx=max(mx,deep[k].y1); mn=min(mn,deep[k].x1);mn=min(mn,deep[k].y1); for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&!flag[edge[i].to]) { deep[edge[i].to].x=deep[k].x; deep[edge[i].to].y=deep[k].y; if (edge[i].color>0) deep[edge[i].to].x++,deep[edge[i].to].y-=2; else deep[edge[i].to].y++,deep[edge[i].to].x-=2; deep[edge[i].to].x1=deep[k].x1; deep[edge[i].to].y1=deep[k].y1; if (edge[i].color>0) deep[edge[i].to].x1--,deep[edge[i].to].y1+=2; else deep[edge[i].to].y1--,deep[edge[i].to].x1+=2; len[edge[i].to]=1ll*len[k]*edge[i].len%P; make(edge[i].to,k); } } void work(int k,int from,int x) { if (x==1) bitreap::Insert(deep[k].x1,deep[k].y1); else bitreap::Delete(deep[k].x1,deep[k].y1); for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&!flag[edge[i].to]) work(edge[i].to,k,x); } void calc(int k,int from) { ans=1ll*ans*ksm(len[k],bitreap::Query(deep[k].x,deep[k].y))%P; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&!flag[edge[i].to]) calc(edge[i].to,k); } void solve(int k) { makes(k,k); k=findroot(k,size[k],k); flag[k]=1;deep[k].x=deep[k].y=deep[k].x1=deep[k].y1=0;len[k]=1;mx=mn=0;make(k,k); bitreap::cnt=0; for (int i=0;i<=mx-mn+1;i++) bitreap::root[i]=0; work(k,k,1); for (int i=p[k];i;i=edge[i].nxt) if (!flag[edge[i].to]) { work(edge[i].to,edge[i].to,-1); calc(edge[i].to,edge[i].to); work(edge[i].to,edge[i].to,1); } for (int i=p[k];i;i=edge[i].nxt) if (!flag[edge[i].to]) solve(edge[i].to); } signed main() { #ifndef ONLINE_JUDGE freopen("b.in","r",stdin); freopen("b.out","w",stdout); #endif srand(time(0)); n=read(); for (int i=1;i<n;i++) { int x=read(),y=read(),z=read(),c=read();if (c==0) c=-1; addedge(x,y,z,c),addedge(y,x,z,c); } solve(1); cout<<ans; return 0; //NOTICE LONG LONG!!!!! }
E:注意到被超过两条线段覆盖的区间是一定无法作出贡献的。如果去掉这些区间,线段之间的关系看起来就比较友好了。
看起来可能需要二分个答案,但就算单组能做也不太能拓展到多组询问上,于是不管。考虑扫描线,这样就能对每个前缀考虑删除线段的贡献了。
每个时刻对线已扫过的部分,维护每个线段单独覆盖的区间长度、两线段只被这两条线段覆盖的区间交的长度(显然每条线段至多和其他两条线段有这样的交区间)。
考虑当前区间被几条线段覆盖。
如果没有,显然可以直接计入贡献。如果超过两条,显然不用管。
如果是两条,就更新他们交的长度,并在此考虑且只考虑同时删除他们的答案。这样所有删除了两条有交线段的情况都可以被考虑到,因为每条线段与左端点在它之前的线段中的至多一条线段有交。
如果只有一条,更新其单独覆盖的区间长度,并考虑选择它和其之前一条与其没有交的线段的最大贡献。这个东西显然随便拿一个数据结构都能维护了。