A:显然每种字符的代价互不相关,dp并打表可得合并i个字符的最小代价是i*(i-1)/2。然后直接贪心分配每个字符即可。因为每次分配都将剩余代价降到了根号级别所以字符数量是足够的。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 100010 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; signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif n=read()*2;if (n==0) {cout<<'a';return 0;} for (int i=0;i<26;i++) { for (int j=n;j>=2;j--) if (1ll*j*(j-1)<=n) { for (int k=1;k<=j;k++) putchar('a'+i); n-=1ll*j*(j-1); break; } if (n==0) break; } return 0; //NOTICE LONG LONG!!!!! }
B:先找出每个会撞在一起的集合,然后可以发现相当于是在网格图中向某方向走遇到一个格点就拐个弯,讨论一下即可。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 100010 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,w,h,ansx[N],ansy[N]; struct data { int op,x,t,i; bool operator <(const data&a) const { return x-t<a.x-a.t; } }a[N]; bool cmp(const data&a,const data&b) { return a.op<b.op||a.op==b.op&&a.x<b.x; } signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif n=read(),w=read(),h=read(); for (int i=1;i<=n;i++) a[i].op=read(),a[i].x=read(),a[i].t=read(),a[i].i=i; sort(a+1,a+n+1); for (int i=1;i<=n;i++) { int t=i; while (t<n&&a[t+1].x-a[t+1].t==a[i].x-a[i].t) t++; sort(a+i,a+t+1,cmp); int x=0,y=0; for (int j=i;j<=t;j++) if (a[j].op==1) x++;else y++; for (int j=i;j<i+x;j++) { int W=i+x-j-1,H=y,X=min(W,H); if (W>=H) ansx[a[j].i]=a[j+X].x,ansy[a[j].i]=h; else ansx[a[j].i]=w,ansy[a[j].i]=a[i+x+X].x; } for (int j=i+x;j<=t;j++) { int W=t-j,H=x,X=min(W,H); if (W>=H) ansx[a[j].i]=w,ansy[a[j].i]=a[j+X].x; else ansx[a[j].i]=a[i+X].x,ansy[a[j].i]=h; } i=t; } for (int i=1;i<=n;i++) printf("%d %d ",ansx[i],ansy[i]); return 0; //NOTICE LONG LONG!!!!! }
C:显然相当于求区间相邻两相同数的位置差之和。于是可以看成一个二维查询,即r前缀中所有前驱>=l的位置的权值和。可以树状数组套treap动态维护。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<set> using namespace std; #define ll long long #define N 100010 #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,m,a[N],root[N],cnt; set<int> pos[N]; struct data{int ch[2],p,x,v;ll s; }tree[N<<5]; int pre(int p,int x) { auto it=pos[x].find(p);it--;return *it; } void up(int k){tree[k].s=tree[lson].s+tree[rson].s+tree[k].v;} 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,int v) { if (k==0) {k=++cnt;tree[k].p=rand();tree[k].x=x;tree[k].s=tree[k].v=v;return;} tree[k].s+=v; if (tree[k].x<x) {ins(rson,x,v);if (tree[rson].p>tree[k].p) move(k,1);} else {ins(lson,x,v);if (tree[lson].p>tree[k].p) move(k,0);} } void del(int &k,int x,int v) { if (tree[k].x==x) tree[k].v-=v; else if (tree[k].x<x) del(rson,x,v); else del(lson,x,v); up(k); } ll query(int k,int x) { if (k==0) return 0; if (tree[k].x<x) return query(rson,x); else return tree[rson].s+tree[k].v+query(lson,x); } void Insert(int k,int x){int y=k-x;while (k<=n) ins(root[k],x,y),k+=k&-k;} void Delete(int k,int x){int y=k-x;while (k<=n) del(root[k],x,y),k+=k&-k;} ll Query(int k,int x){ll s=0;while (k) s+=query(root[k],x),k^=k&-k;return s;} signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif srand(20020509); n=read(),m=read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<=n;i++) pos[i].insert(0); for (int i=1;i<=n;i++) { pos[a[i]].insert(i); Insert(i,pre(i,a[i])); } for (int i=1;i<=m;i++) { int op=read(); if (op==1) { int p=read(),x=read(),y=pre(p,a[p]); if (a[p]==x) continue; auto it=pos[a[p]].find(p);it++; if (it!=pos[a[p]].end()) { Delete(*it,p); Insert(*it,y); } pos[a[p]].erase(p); a[p]=x; pos[x].insert(p); int z=pre(p,x); it=pos[x].find(p);it++; if (it!=pos[x].end()) { Delete(*it,z); Insert(*it,p); } Delete(p,y);Insert(p,z); } if (op==2) { int l=read(),r=read(); printf("%I64d ",Query(r,l)); } } return 0; //NOTICE LONG LONG!!!!! }
D:
搬上sol里的图,然后就变得很简单了。考虑初始边所衍生出的每个部分,这样就变成了一个子问题。大体类似于本质不同的有根树的计数,只是稍微复杂一点。
设f[i][j]为操作i次后最小割为j的方案数。考虑先求出一个部分的方案数。即设g[i][j]为操作i次最小割为j(不考虑初始边)且钦定初始边只被操作一次的方案数,转移显然,这一部分是O(n4)的。
然后考虑通过g推回f,由于相同的部分之间是无序的,类似上面的问题做一些操作即可。即设h[i][j][x][y]为操作i次后最小割为j且当前只考虑到(x,y)及其之前(如x'<x||x'==x&&y'<y)的部分的方案数。转移时枚举(x,y)选择多少个,进行一个插板即可完成无序统计。复杂度是极小常数O(n5logn)。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define P 1000000007 #define N 52 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][N],g[N][N],h[N][N][N][N],inv[N],ans; void inc(int &x,int y){x+=y;if (x>=P) x-=P;} int C(int n,int m) { if (m>n) return 0; int s=1; for (int i=n-m+1;i<=n;i++) s=1ll*s*i%P; return 1ll*s*inv[m]%P; } int F(int n,int m){return C(n+m-1,m);} signed main() { #ifndef ONLINE_JUDGE freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif n=read(),m=read();int tmp=m;m=n+1; inv[0]=inv[1]=1;for (int i=2;i<=51;i++) inv[i]=P-1ll*(P/i)*inv[P%i]%P; for (int i=2;i<=50;i++) inv[i]=1ll*inv[i]*inv[i-1]%P; f[0][1]=1;g[0][0]=1; for (int x=0;x<=n;x++) for (int y=0;y<=m;y++) h[0][1][x][y]=1; for (int i=1;i<=n;i++) { for (int j=1;j<=m;j++) { for (int x=0;x<i;x++) { for (int y=j+1;y<=m;y++) inc(g[i][j],1ll*f[x][y]*f[i-x-1][j]%P), inc(g[i][j],1ll*f[x][j]*f[i-x-1][y]%P); inc(g[i][j],1ll*f[x][j]*f[i-x-1][j]%P); } } for (int j=1;j<=m;j++) { for (int x=1;x<=n;x++) for (int y=1;y<=m;y++) { int ux=x,uy=y-1;if (uy==0) ux--,uy=m; h[i][j][x][y]=h[i][j][ux][uy]; for (int k=1;k*x<=i&&k*y<j;k++) inc(h[i][j][x][y],1ll*h[i-x*k][j-y*k][ux][uy]*F(g[x][y],k)%P); } f[i][j]=h[i][j][n][m]; } } m=tmp; cout<<f[n][m]; return 0; //NOTICE LONG LONG!!!!! }