今天World Final。蒟蒻连去看直播的资格都没有啊只好来VP多校了。
2015年的XJ题,大佬辈出的年代啊。
题目:HDU5327~5338
A. Olympiad
题意
问[l,r]区间内有多少各位数字不相同的数。l,r<=1e5。
题解
签到题。模拟即可。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<time.h>
#include<vector>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
using namespace std;
const int N=1e5+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
int n,sum[N],bit[12];
namespace FastIO {
template<typename tp> inline void read(tp &x) {
x=0; register char c=getchar(); register bool f=0;
for(;c<'0'||c>'9';f|=(c=='-'),c = getchar());
for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c = getchar());
if(f) x=-x;
}
template<typename tp> inline void write(tp x) {
if (x==0) return (void) (putchar('0'));
if (x<0) putchar('-'),x=-x;
int pr[20]; register int cnt=0;
for (;x;x/=10) pr[++cnt]=x%10;
while (cnt) putchar(pr[cnt--]+'0');
}
template<typename tp> inline void writeln(tp x) {
write(x);
putchar('\n');
}
}
using namespace FastIO;
int check(int x) {
memset(bit,0,sizeof(bit));
while (x) {
int r=x%10;
if (bit[r]) return 0;
bit[r]=1;
x/=10;
}
return 1;
}
int main() {
int t; read(t);
for (int i=1;i<N;i++) {
sum[i]=sum[i-1]+check(i);
}
for (int i=1;i<=t;i++) {
int a,b; read(a); read(b); cout<<sum[b]-sum[a-1]<<endl;
}
return 0;
}
B. Problem Killer
题意
给定一个长为n的串,求最长的等差或等比子串。
题解
签到题*2,模拟即可。
#include <bits/stdc++.h>
#define int long long
#define fo(i, n) for(int i = 1; i <= (n); i ++)
#define out(x) cerr << #x << " = " << x << "\n"
#define type(x) __typeof((x).begin())
#define foreach(it, x) for(type(x) it = (x).begin(); it != (x).end(); ++ it)
using namespace std;
// by piano
template<typename tp> inline void read(tp &x) {
x = 0;char c = getchar(); bool f = 0;
for(; c < '0' || c > '9'; f |= (c == '-'), c = getchar());
for(; c >= '0' && c <= '9'; x = (x << 3) + (x << 1) + c - '0', c = getchar());
if(f) x = -x;
}
template<typename tp> inline void arr(tp *a, int n) {
for(int i = 1; i <= n; i ++)
cout << a[i] << " ";
puts("");
}
namespace one {
static const int N = 2e6 + 233;
int n, ans, a[N], l, r;
inline void upd(void) {
ans = max(ans, r - l + 1);
}
int main(void) {
ans = 0;
read(n);
for(int i = 1; i <= n; i ++) read(a[i]);
if(n <= 1) return cout << "1\n", 0;
l = 1, r = 2;
upd();
for(r = 3; r <= n; r ++) {
int delta = a[r] - a[r - 1];
while(l < r && a[l + 1] - a[l] != delta)
++ l;
upd();
}
l = 1, r = 2;
upd();
for(r = 3; r <= n; r ++) {
while(l < r && a[l + 1] * a[r - 1] != a[r] * a[l])
++ l;
upd();
}
cout << ans << "\n";
}
}
main(void) {
int T; for(read(T); T --;) one::main();
}
C. Question for the Leader
题意
给定一棵n个节点的基环树,问存在多少个k,使得可以将其分为n/k个联通块,每个联通块中包含k个节点。
n<=100000
题解
对于树,有一个结论:如果可以将这棵树分为大小相同的n/k份,那么子树大小是k的倍数的节点数有n/k个。(根可以任选)
但这是一棵基环树。那我们先把环扣出来,暴力枚举删掉哪条边,然后就可以套用上面的结论了。
直接模拟是O(n^2)的,考虑优化。
先把非环上的点的size算好,然后考虑环上的点。将环上所有的点按序标号,size[i]表示第i个点挂下的子树大小,sum[i]表示前i个点挂下的子树大小和。
当我们将第i个点和第(i-1)个点之间的边断开时,j号点的子树大小为(sum[j]-sum[i])。对于一种约数x,只要(sum[j]%x==sum[i]%x),j号点的子树大小就是x的倍数了。在预处理后可以O(1)统计。
注意要加上非环点的贡献。最终答案应该是所有断边方案中可取约数的并集,复杂度O(n^1.5)
代码挺恶心的……
#include<bits/stdc++.h>
#define R register
using namespace std;
const int N=1e5+10;
int n,m,E,tim;
int fa[N],fir[N],nex[N<<1],arr[N<<1];
int c[N],a[N],siz[N],num[N],dfn[N],sum[N],md[150][N],cnt[N],can[N];
vector<int> vec,cir;
inline void Add_Edge(int x,int y) {
nex[++E]=fir[x];
fir[x]=E; arr[E]=y;
}
void Init() {
tim=E=0;
memset(fir,0,sizeof(int)*(n+5));
memset(num,0,sizeof(int)*(n+5));
memset(c,0,sizeof(int)*(n+5));
memset(dfn,0,sizeof(int)*(n+5));
vec.clear(); cir.clear();
for (R int i=1;i<=n;i++)
if (n%i==0) vec.push_back(i);
for (R int i=1;i<=n;i++) {
int x; scanf("%d",&x);
Add_Edge(i,x); Add_Edge(x,i);
}
for (int i=0;i<vec.size();i++)
memset(md[i],0,sizeof(int)*(vec[i]+3));
memset(can,0,sizeof(int)*(vec.size()+3));
}
void getcir(int x) {
dfn[x]=++tim;
for (int i=fir[x];i;i=nex[i]) {
if (arr[i]==fa[x]) continue;
if (!dfn[arr[i]]) fa[arr[i]]=x,getcir(arr[i]);
else if (dfn[arr[i]]>dfn[x]) {
int y=arr[i];
cir.push_back(x); c[x]=1;
do {
cir.push_back(y);
c[y]=1;
y=fa[y];
}while (x!=y);
}
}
}
void dfs(int x,int fa) {
siz[x]=1;
for (int i=fir[x];i;i=nex[i]) {
if (!c[arr[i]]&&arr[i]!=fa) {
dfs(arr[i],x); siz[x]+=siz[arr[i]];
}
}
}
void check(int x) {
int v=vec.size();
for (int i=0;i<v;i++) {
cnt[i]=md[i][(sum[x]+vec[i])%vec[i]]+num[i];
if (cnt[i]*vec[i]==n) can[i]=1;
}
}
void Solve() {
Init();
getcir(1);
for (int i=1;i<=n;i++)
if (c[i]) dfs(i,0);
for (R int i=1;i<=n;i++)
if (!c[i]) {
int v=vec.size();
for (R int j=0;j<v;j++) {
if (siz[i]%vec[j]==0) num[j]++;
}
}
int v=vec.size();
for (int i=0;i<cir.size();i++) {
sum[i]=(i)?sum[i-1]+siz[cir[i]]:siz[cir[i]];
for (int j=0;j<v;j++) md[j][sum[i]%vec[j]]++;
}
for (int i=0;i<cir.size();i++) check(i);
int ans=0;
for (int i=0;i<vec.size();i++) ans+=can[i];
printf("%d\n",ans);
}
int main() {
while (cin>>n) Solve();
return 0;
}
D. Route Statistics
题意
给定m维坐标中的n个点,坐标范围是{0,1,2},对于每个k∈[0,2m],求出有多少个点对的曼哈顿距离是k。
m<=11,n<=300000
题解
神奇的状压DP……
我们要计算的是到每个串距离为x的串有多少个。用f[i][s][j]表示与串s的前i位都相同的那些串,到s距离为j的个数。
初始状态f[m][s][0]=串s出现次数。只要转移出f[0][s][x]就可以了。
假设串s第i位是c,那么就有:
最后统计答案的时候注意特判x=0
复杂度O(m^2*3^m)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=300005;
LL ans[30];
int n,m,T,pw[20],cnt[N],f[2][N][30];
char s[30];
void Solve() {
scanf("%d%d",&n,&m);
memset(cnt,0,sizeof(int)*(pw[m]+3));
for (int i=1;i<=n;i++) {
scanf("%s",s+1);
int x=0;
for (int i=1;i<=m;i++) x=x*3+s[i]-'0';
cnt[x]++;
}
for (int i=0;i<m;i++)
for (int j=0;j<pw[m];j++)
memset(f[0][j],0,sizeof(int)*(2*m+2));
for (int i=0;i<pw[m];i++) f[0][i][0]=cnt[i];
int now=1;
for (int i=0;i<m;i++) {
for (int j=0;j<pw[m];j++)
memset(f[now][j],0,sizeof(int)*(2*m+2));
for (int j=0;j<pw[m];j++)
for (int k=0;k<=2*m;k++) {
if (!f[now^1][j][k]) continue;
int x=j/pw[i]%3;
for (int l=0;l<=2;l++) {
int to=j+(l-x)*pw[i];
f[now][to][k+abs(x-l)]+=f[now^1][j][k];
}
}
now^=1;
}
memset(ans,0,sizeof(ans));
for (int i=0;i<pw[m];i++) {
if (!cnt[i]) continue;
ans[0]+=1LL*cnt[i]*(cnt[i]-1);
for (int j=1;j<=2*m;j++) ans[j]+=1LL*cnt[i]*f[now^1][i][j];
}
for (int i=0;i<=2*m;i++) printf("%lld\n",ans[i]/2);
}
int main() {
pw[0]=1;
for (int i=1;i<=12;i++) pw[i]=pw[i-1]*3;
scanf("%d",&T);
for (int i=1;i<=T;i++) Solve();
return 0;
}
E. Simple Problem
题意
一开始有一个点0,进行(n-1)次加点操作,每次保证加入后的图仍是一棵树,输出每次加点后树的最大独立集。
n<=100000
题解
因为树是二分图,只需求出最大匹配数就可以了。
如果树的形态不变,显然有这样的贪心策略:用f[i]表示i号点是否和它的一个孩子匹配,那么,
再考虑加点操作。我们用a[i]表示i号点的孩子中f为0的个数,那么加入一个点x后会影响的点是满足以下条件的点y组成的链:
1、y是x的祖先;
2、y到x的路径中,距离x为奇数的点a为0,为偶数的点a为1。
那我们就可以事先把树建好,树链剖分,每次直接修改即可。
线段树需要记录:f的和,深度为奇数、偶数的a的最大值。
一段x的祖先的区间,如果距离x为奇数的点a最大值为0,为偶数的点a最大值为1,这段区间会被影响。这是因为一个点如果有孩子的a是0,这个点的a至少是1。
修改的时候,被影响区间的f被翻转,a按深度奇偶性加减。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,E,y,tim;
int fir[N],nex[N<<1],arr[N<<1];
int pos[N],son[N],siz[N],fa[N],top[N],dep[N],dfn[N];
int len[N<<2],val[N<<2],rev[N<<2],mx[N<<2][2],tag[N<<2][2];
inline void Add_Edge(int x,int y) {
nex[++E]=fir[x];
fir[x]=E; arr[E]=y;
}
void dfs1(int x) {
siz[x]=1; son[x]=0;
for (int i=fir[x];i;i=nex[i]) {
if (arr[i]==fa[x]) continue;
dfs1(arr[i]);
siz[x]+=siz[arr[i]];
if (siz[arr[i]]>siz[son[x]]) son[x]=arr[i];
}
}
void dfs2(int x) {
dfn[pos[x]=++tim]=x;
if (son[x]) top[son[x]]=top[x],dfs2(son[x]);
for (int i=fir[x];i;i=nex[i]) {
if (arr[i]==fa[x]||arr[i]==son[x]) continue;
else top[arr[i]]=arr[i],dfs2(arr[i]);
}
}
void Build(int x,int l,int r) {
val[x]=mx[x][0]=mx[x][1]=tag[x][0]=tag[x][1]=rev[x]=0;
len[x]=r-l+1;
if (len[x]==1) return;
int mid=(l+r)>>1;
Build(x<<1,l,mid); Build(x<<1|1,mid+1,r);
}
inline void Pushdown(int x) {
for (int i=0;i<2;i++) {
if (!tag[x][i]) continue;
mx[x<<1][i]+=tag[x][i];
mx[x<<1|1][i]+=tag[x][i];
tag[x<<1][i]+=tag[x][i];
tag[x<<1|1][i]+=tag[x][i];
tag[x][i]=0;
}
if (rev[x]) {
val[x<<1]=len[x<<1]-val[x<<1];
val[x<<1|1]=len[x<<1|1]-val[x<<1|1];
rev[x<<1]^=1; rev[x<<1|1]^=1;
rev[x]=0;
}
}
inline void Pushup(int x) {
for (int i=0;i<2;i++) mx[x][i]=max(mx[x<<1][i],mx[x<<1|1][i]);
val[x]=val[x<<1]+val[x<<1|1];
}
void Query(int x,int l,int r,int lt,int rt,int t) {
if (lt<=l&&r<=rt) {
if (mx[x][t]<=1&&mx[x][t^1]<=0) return (void) (y=l);
if (l==r) return (void) (y=(!y)?(n+1):y);
}
Pushdown(x);
int mid=(l+r)>>1;
if (mid<rt) {
Query(x<<1|1,mid+1,r,lt,rt,t);
if (y>mid+1) return;
}
if (lt<=mid) Query(x<<1,l,mid,lt,rt,t);
}
void Updata(int x,int l,int r,int lt,int rt,int t) {
if (lt<=l&&r<=rt) {
tag[x][t]--; tag[x][t^1]++;
mx[x][t]--; mx[x][t^1]++;
return;
}
Pushdown(x);
int mid=(l+r)>>1;
if (mid>=lt) Updata(x<<1,l,mid,lt,rt,t);
if (mid<rt) Updata(x<<1|1,mid+1,r,lt,rt,t);
Pushup(x);
}
void Reverse(int x,int l,int r,int lt,int rt) {
if (lt<=l&&r<=rt) {
val[x]=len[x]-val[x];
rev[x]^=1;
return;
}
Pushdown(x);
int mid=(l+r)>>1;
if (mid>=lt) Reverse(x<<1,l,mid,lt,rt);
if (mid<rt) Reverse(x<<1|1,mid+1,r,lt,rt);
Pushup(x);
}
void Modify(int x,int t) {
for (;x;x=fa[top[x]]) {
y=0;
Query(1,1,n,pos[top[x]],pos[x],t);
if (y==n+1) return (void) Updata(1,1,n,pos[x],pos[x],t);
y=dfn[y];
Updata(1,1,n,pos[y],pos[x],t);
Reverse(1,1,n,pos[y],pos[x]);
if (y!=top[x]) {
y=fa[y];
Updata(1,1,n,pos[y],pos[y],t);
return;
}
}
}
void Solve() {
E=tim=0;
memset(fir,0,sizeof(int)*(n+3));
for (int i=2;i<=n;i++) {
scanf("%d",&fa[i]);
Add_Edge(i,++fa[i]);
Add_Edge(fa[i],i);
dep[i]=dep[fa[i]]+1;
}
dfs1(1); top[1]=1; dfs2(1);
Build(1,1,n);
for (int i=2;i<=n;i++) {
Modify(fa[i],dep[i]&1);
printf("%d\n",i-val[1]);
}
}
int main() {
while (~scanf("%d",&n)) Solve();
return 0;
}
F. Test for Rikka
题意
构造一个n*n的矩阵A,令B=A^m,使得B[1][n]=k,读入k,n和m可以任选。
k<=1e18,n<=30,m<=30。
题解
神题……
显然如果将这个矩阵看做图的邻接矩阵,就是点1走m步到点n的方案数为k。
一开始想的是将图分层,低层向高层连边,终点加自环,这样可以控制步数,但是方案数只能到1e9。
要控制步数,要么让图变成一张DAG,要么就来一张完全图!
这样构造图:从点1出发,连向一个大小为C的团。团后面接一条长为d的链,链的底端是点n。
考虑团中的一个点x,走了i步后到达点x的方案数显然是C^(i-1),那么如果将x向链上距点n距离为t的点连边,就会对答案产生C^(m-t-1)的贡献。
不妨取C=9,d=20,m=21,易知可以构造出k<=1e18的所有图。
#include<bits/stdc++.h>
using namespace std;
long long k,pw[22];
int a[35][35];
void Solve() {
memset(a,0,sizeof(a));
for (int i=2;i<=10;i++) a[1][i]=1;
for (int i=2;i<=10;i++)
for (int j=2;j<=10;j++) a[i][j]=1;
for (int i=11;i<=29;i++) a[i][i+1]=1;
scanf("%lld",&k);
int now=19;
while (k) {
while (k<pw[now]&&now) now--;
for (int i=2;k>=pw[now];k-=pw[now],i++) a[i][now+11]=1;
}
puts("30 21");
for (int i=1;i<=30;i++) {
for (int j=1;j<=30;j++) printf("%d",a[i][j]);
putchar('\n');
}
}
int main() {
pw[0]=1;
for (int i=1;i<=19;i++) pw[i]=pw[i-1]*9;
int T; scanf("%d",&T);
while (T--) Solve();
return 0;
}
G. Undirected Graph
题意
给定一张n个点m条边的无向图,q次询问,每次询问如果只保留左右端点都在区间[l,r]中的边,原图有几个联通块。
n<=100000,m<=200000,q<=200000
题解
考虑离线。如果所有询问关于右端点排序,可选的边就是右端点不超过询问右端点的边,且这些边中左端点越靠右,越容易减小联通块个数。
那么将边也关于右端点排序。当询问的右端点右移,相应地也将边的右端点右移。每次加入一条边的时候,如果连接的两个点不连通,直接加入;否则,代替掉两点路径上左端点最靠左的边(如果这条边的左端点比加入边的左端点靠左)。这样就需要用一颗LCT维护最小生成树森林。
询问的时候,每多一条左端点在询问左端点右边的边,就会使联通块个数-1,树状数组维护一下即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
const int V=1e5+5;
const int inf=0x3f3f3f3f;
int n,m,q,tot;
int mi[N],ch[N][2],rev[N],st[N],tree[N],fa[N],ans[N];
struct Pair {
int u,v,tim;
bool operator < (Pair &other) const {
return v<other.v;
}
}E[N],Q[N];
namespace FastIO {
template<typename tp> inline void read(tp &x) {
x=0; register char c=getchar(); register bool f=0;
for(;c<'0'||c>'9';f|=(c=='-'),c = getchar());
for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c = getchar());
if(f) x=-x;
}
template<typename tp> inline void write(tp x) {
if (x==0) return (void) (putchar('0'));
if (x<0) putchar('-'),x=-x;
int pr[20]; register int cnt=0;
for (;x;x/=10) pr[++cnt]=x%10;
while (cnt) putchar(pr[cnt--]+'0');
}
template<typename tp> inline void writeln(tp x) {
write(x);
putchar('\n');
}
}
using namespace FastIO;
inline int Get(int x) {
return (ch[fa[x]][1]==x);
}
inline int Isroot(int x) {
return (ch[fa[x]][0]!=x)&&(ch[fa[x]][1]!=x);
}
inline void Pushup(int x) {
if (E[mi[ch[x][0]]].u<E[mi[ch[x][1]]].u) mi[x]=mi[ch[x][0]];
else mi[x]=mi[ch[x][1]];
if (x<=V) return;
if (E[x-V].u<E[mi[x]].u) mi[x]=x-V;
}
inline void Pushdown(int x) {
if (!rev[x]) return;
swap(ch[x][0],ch[x][1]);
rev[ch[x][0]]^=1;
rev[ch[x][1]]^=1;
rev[x]=0;
}
inline void Rotate(int x) {
int y=fa[x],z=fa[y],k=Get(x);
if (!Isroot(y)) ch[z][Get(y)]=x;
fa[x]=z;
fa[ch[x][k^1]]=y;
ch[y][k]=ch[x][k^1];
ch[x][k^1]=y;
fa[y]=x;
Pushup(y); Pushup(x);
}
inline void Splay(int x) {
int top=0;
st[++top]=x;
for (int t=x;!Isroot(t);t=fa[t]) st[++top]=fa[t];
for (;top;top--) Pushdown(st[top]);
for (;!Isroot(x);Rotate(x)) {
if (!Isroot(fa[x])) (Get(x)==Get(fa[x]))?Rotate(fa[x]):Rotate(x);
}
Pushup(x);
}
inline void Access(int x) {
for (int t=0;x;t=x,x=fa[x]) {
Splay(x); ch[x][1]=t; Pushup(x);
}
}
inline int Find(int x) {
Access(x); Splay(x);
while (ch[x][0]) x=ch[x][0];
Splay(x); return x;
}
inline void Evert(int x) {
Access(x); Splay(x); rev[x]^=1;
}
inline void Link(int x,int y) {
Evert(x); fa[x]=y;
}
inline void Cut(int x,int y) {
Evert(x); Access(y); Splay(y);
ch[y][0]=fa[x]=0;
}
inline int Query(int x,int y) {
Evert(x); Access(y); Splay(y);
return mi[y];
}
inline void Updata(int x,int del) {
while (x<=n) tree[x]+=del,x+=(x&-x);
}
inline int ask(int x) {
int ans=0;
while (x) ans+=tree[x],x-=(x&-x);
return ans;
}
void Insert(int x) {
if (E[x].u==E[x].v) return;
if (Find(E[x].u)!=Find(E[x].v)) {
++tot; Updata(E[x].u,1);
Link(x+V,E[x].u); Link(x+V,E[x].v);
return;
}
int t=Query(E[x].u,E[x].v);
if (E[t].u>E[x].u) return;
Updata(E[t].u,-1);
Cut(t+V,E[t].u); Cut(t+V,E[t].v);
Updata(E[x].u,1);
Link(x+V,E[x].u); Link(x+V,E[x].v);
}
void Init() {
tot=0;
memset(tree,0,sizeof(int)*(n+5));
memset(ans,0,sizeof(int)*(q+5));
for (int i=1;i<=n;i++) ch[i][0]=ch[i][1]=fa[i]=rev[i]=mi[i]=0;
for (int i=1+V;i<=m+1+V;i++) ch[i][0]=ch[i][1]=fa[i]=rev[i]=mi[i]=0;
}
int main() {
while (~scanf("%d%d%d",&n,&m,&q)) {
for (int i=1;i<=m;i++) {
read(E[i].u); read(E[i].v);
if (E[i].u>E[i].v) swap(E[i].u,E[i].v);
}
for (int i=1;i<=q;i++) {
read(Q[i].u); read(Q[i].v); Q[i].tim=i;
}
sort(E+1,E+m+1); sort(Q+1,Q+q+1);
E[0].u=inf; for (int i=1;i<=m;i++) mi[i+V]=i;
for (int i=1,j=0;i<=q;i++) {
while (j<m&&E[j+1].v<=Q[i].v) {
++j;
Insert(j);
}
ans[Q[i].tim]=n-tot+ask(Q[i].u-1);
}
for (int i=1;i<=q;i++) writeln(ans[i]);
Init();
}
return 0;
}
H. Virtual Participation
题意
给定整数k<=1e9,构造一个串,使它不同的子串个数恰好为k,串长不大于100000。
题解
大瞎搞题,糊了个和标算不同的做法。
看到题第一反应就是,每次将相同的数放若干次。
假设我们放第i个数xi次,总共放了r个数,那么不同的子串个数就是:
但是这个式子很难处理。于是考虑缩减r的范围。
如果r=2,设放了x个1,y个2,那么子串个数就是(x+y+xy),因式分解一下:
这个式子表明如果(k+1)可以分解为两个和不超过100002的整数的乘积,我们就可以构造出解。
这样不能处理(k+1)为质数。考虑把r扩展到3:
枚举x=1,2后发现有规律。不妨把x当做常数,那么就有:
这时只要k+(x+1)^2-x可以分解为两个和不超过100000的整数的乘积就行了。不妨将x从1开始枚举,只需很少的次数就可枚举出解。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<time.h>
#include<vector>
#include<cstring>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define R register
#define LL long long
using namespace std;
const int N=1e5+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
int n,k,T;
namespace FastIO {
template<typename tp> inline void read(tp &x) {
x=0; register char c=getchar(); register bool f=0;
for(;c<'0'||c>'9';f|=(c=='-'),c = getchar());
for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c = getchar());
if(f) x=-x;
}
template<typename tp> inline void write(tp x) {
if (x==0) return (void) (putchar('0'));
if (x<0) putchar('-'),x=-x;
int pr[20]; register int cnt=0;
for (;x;x/=10) pr[++cnt]=x%10;
while (cnt) putchar(pr[cnt--]+'0');
}
template<typename tp> inline void writeln(tp x) {
write(x);
putchar('\n');
}
}
using namespace FastIO;
void Solve() {
for (int x=1;x<=1000;x++) {
int t=k+(x+1)*(x+1)-x;
for (int i=x+1;i*i<=t;i++) {
if (t%i) continue;
int y=i-x-1,z=t/i-x-1;
if (y<0||z<0) continue;
if (x+y+z>100000) continue;
printf("%d\n",x+y+z);
for (int i=1;i<x;i++) putchar('1'),putchar(' ');
if (!y) y=z,z=0;
if (x) {
putchar('1');
if (y||z) putchar(' ');
}
for (int i=1;i<y;i++) putchar('2'),putchar(' ');
if (y) {
putchar('2');
if (z) putchar(' ');
}
for (int i=1;i<z;i++) putchar('3'),putchar(' ');
if (z) putchar('3');
putchar('\n');
return;
}
}
throw;
}
main() {
while (~scanf("%d",&k)) Solve();
return 0;
}
I. Walk Out
题意
给定一个n*m的迷宫,每个格子上有0或1。可以上下左右移动,求(1,1)到(n,m)的最短路。
路径长度的定义是:将所有走过格子上的数字按次序写下后得到的二进制数的大小。
去前导零后输出二进制数,n,m<=1000。
题解
VP时我切的第二题。不难但挺精彩的一道题。
不能跑最短路的瓶颈在于dist的比较是O(n)的。一开始想优化它但是没有想到好的办法。
后面突然想到:如果(1,1)上的数字是1,那最优解一定也是曼哈顿距离最小的解。
顺着这个思路想下去。如果走过的路径上已经有了1,那么只能向下或向右走;否则可以向四个方向走。
将搜索分为两个阶段:
1、只走0的格子。在可以走到的范围内找出离终点曼哈顿距离最小的1,将他们入队。
2、取出这些1,记录答案,再往下BFS。
显然每个点只会入队出队一次,复杂度O(n*m)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
const int u[4]={1,0,-1,0};
const int v[4]={0,1,0,-1};
char s[N];
int T,n,m,cnt,ans_num,type;
int dis[N],a[N][N],ans[N*10],vis[N][N];
struct node {
int x,y,dis,To_end;
}tmp[N];
queue<node> Q;
void Init() {
memset(ans,0,sizeof(ans));
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
cnt=0; ans_num=0;
}
void Getplace() {
vis[1][1]=1;
if (a[1][1]) return (void) (tmp[++cnt]=(node) {1,1,1,n-1+m-1});
Q.push((node){1,1,1,n-1+m-1});
while (!Q.empty()) {
int x=Q.front().x,y=Q.front().y,d=Q.front().dis; Q.pop();
for (int i=0;i<4;i++) {
int xx=x+u[i],yy=y+v[i],To_end=n-xx+m-yy;
if (xx>n||xx<=0||yy>m||yy<=0) continue;
if (!vis[xx][yy]) {
vis[xx][yy]=1;
if (!a[xx][yy]) Q.push((node){xx,yy,d+1,To_end});
else {
if (cnt&&To_end<tmp[cnt].To_end) tmp[cnt=1]=(node) {xx,yy,d+1,To_end};
else if (!cnt||To_end==tmp[cnt].To_end) tmp[++cnt]=(node) {xx,yy,d+1,To_end};
}
}
}
}
}
void Solve() {
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) {
scanf("%s",s+1);
for (int j=1;j<=m;j++) a[i][j]=s[j]-'0';
}
type=a[n][m]; a[n][m]=1;
Init();
Getplace();
while (cnt) {
ans[++ans_num]=tmp[1].dis;
for (int i=1;i<=cnt;i++) Q.push(tmp[i]);
cnt=0;
while (!Q.empty()) {
int x=Q.front().x,y=Q.front().y,d=Q.front().dis; Q.pop();
for (int i=0;i<2;i++) {
int xx=x+u[i],yy=y+v[i],To_end=n-xx+m-yy;
if (xx>n||xx<=0||yy>m||yy<=0) continue;
if (!vis[xx][yy]) {
vis[xx][yy]=1;
if (!a[xx][yy]) Q.push((node){xx,yy,d+1,To_end});
else {
if (cnt&&To_end<tmp[cnt].To_end) tmp[cnt=1]=(node) {xx,yy,d+1,To_end};
else if (!cnt||To_end==tmp[cnt].To_end) tmp[++cnt]=(node) {xx,yy,d+1,To_end};
}
}
}
}
}
int now=ans[1];
for (int i=1;i<=ans_num;i++) {
if (i>1) for (;now<ans[i];now++) putchar('0');
if (i!=ans_num) putchar('1');
else cout<<type;
now++;
}
putchar('\n');
}
int main() {
scanf("%d",&T);
while (T--) Solve();
return 0;
}
J. XYZ and Drops
题意
模拟游戏十滴水……
题解
按题意模拟即可
跟DHL看了半天查不出错,一气之下重构代码就AC了
#include<bits/stdc++.h>
using namespace std;
const int N=105;
const int u[4]={1,-1,0,0};
const int v[4]={0,0,1,-1};
int r,c,n,T,x[N],y[N];
int siz[N],tim[N],id[N][N];
struct drop {
int x,y,dx,dy;
};
vector<int> wat;
vector<drop> vec,tmp;
void Push(int x,int y) {
for (int i=0;i<4;i++) tmp.push_back((drop){x,y,u[i],v[i]});
}
void Init() {
vec.clear(); tmp.clear(); wat.clear();
memset(id,0,sizeof(id));
memset(tim,0,sizeof(tim));
for (int i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&siz[i]);
for (int i=1;i<=n;i++) id[x[i]][y[i]]=i;
int xx,yy; scanf("%d%d",&xx,&yy); Push(xx,yy);
}
void Doit(int T) {
vec.clear();
for (int i=0;i<tmp.size();i++) vec.push_back(tmp[i]);
tmp.clear();
wat.clear();
for (int i=0;i<vec.size();i++) {
drop t=vec[i];
int xx=t.x+t.dx,yy=t.y+t.dy;
if (xx<=0||xx>r||yy<=0||yy>c) continue;
int p=id[xx][yy];
if (siz[p]) {
if (++siz[p]>=5&&!tim[p]) {
tim[p]=T;
wat.push_back(p);
}
}
else tmp.push_back((drop){xx,yy,t.dx,t.dy});
}
for (int i=0;i<wat.size();i++) {
siz[wat[i]]=0;
Push(x[wat[i]],y[wat[i]]);
}
}
void Solve() {
Init();
for (int i=1;i<=T;i++) Doit(i);
for (int i=1;i<=n;i++) {
if (siz[i]) printf("1 %d\n",siz[i]);
else printf("0 %d\n",tim[i]);
}
}
int main() {
while (cin>>r>>c>>n>>T) Solve();
return 0;
}
K. Yet Another XYZ Problem
题意
给定两个由x、y、z组成的字符串S,T,每次可以进行下面三个操作:
1、将S中所有x变成y,代价C0;
2、将S中所有y变成z,代价C1;
3、将S中所有z变成x,代价C2。
问将S变换成T,在代价不超过Maxcost的情况下有多少种方案,对1e9+7取模。
C0,C1,C2,Maxcost<=1e18,输入数据不超过50KB
题解
先吐槽一发……
据说wys的毒瘤是跟xyz学的???
惊恐的眼神……
吓得我都打出了:
return (void) (printf("%d\n"),getmod(ans));
更恐怖的是,调代码的时候第一组把我卡掉的数据是:
1
1 1 1 10
xyz
xxy
来自大佬的嘲讽……幸运的是身体还算健康
分类讨论题,大致分为7类。用cnt1记S中出现字母种类数,cnt2记T中出现字母种类数,sum=C0+C1+C2
1、cnt1<cnt2,直接puts(“0”);
2、cnt1=1,cnt2=1。设将S变成T的最小费用是x,那么答案显然就是:
3、cnt1=2,cnt2=2。这时每次变换所能改变的字母是确定的,否则改变另一个字母会使两个字母变成相同就变不回来了。然后同情况2计算即可。要注意的是有可能永远不能变成相同,比如”xzz”和”xyx”
4、cnt1=3,cnt2=3。S、T相同答案是1,否则为0
5、cnt1=3,cnt2=2。枚举第一次变换变了什么字母,然后同情况3计算即可。
6、cnt1=2,cnt2=1。显然是两个字母先变,变到某处后变成一个字母,然后再进行若干次变换。因为6是两种变换的循环节,这时要枚举变成一个字母所用步数关于6的余数,这时一个字母变换次数关于6取模有两种可能。不考虑变循环节的费用,假设前者所需费用是Cx,后者是Cy,那么前后可变循环节次数的最大值是:
这时对答案产生的贡献是
7、cnt1=3,cnt2=1。枚举第一次变换变了什么字母,然后同情况6计算即可。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
LL c[3],cx;
int n,m,len,to[3],a[3][2];
char s[N],sx[N],t[N];
inline int nex(int x) {
return (x==2)?0:x+1;
}
inline int getmod(LL x) {
x%=mod;
return (x<0)?x+mod:x;
}
int calc1(char *s,char *t,LL cc) {
int a=s[1]-'x',b=t[1]-'x';
while (a!=b) cc+=c[a],a=nex(a);
if (cc>cx) return 0;
return getmod((cx-cc)/(c[0]+c[1]+c[2])+1);
}
int calc2(char *s,char *t,LL cc) {
memset(to,0,sizeof(to));
for (int i=1;i<=len;i++) {
if (!to[s[i]-'x']) to[s[i]-'x']=t[i];
if (to[s[i]-'x']!=t[i]) return 0;
}
int a=s[1],u=t[1],b,v;
for (int i=1;i<=len;i++) {
if (s[i]!=a) b=s[i];
if (t[i]!=u) v=t[i];
}
a-='x'; b-='x'; u-='x'; v-='x';
while (!(a==u&&b==v)) {
if (nex(a)==b) cc+=c[b],b=nex(b);
else if (nex(b)==a) cc+=c[a],a=nex(a);
}
if (cc>cx) return 0;
else return getmod((cx-cc)/(c[0]+c[0]+c[1]+c[1]+c[2]+c[2])+1);
}
int calc3(char *s,char *t) {
for (int i=1;i<=len;i++)
if (s[i]!=t[i]) return 0;
return 1;
}
int calc4(char *s,char *t,LL cc) {
int a=s[1],u=t[1],b; LL ans=0;
for (int i=1;i<=len;i++) {
if (s[i]!=a) b=s[i];
}
a-='x'; b-='x'; u-='x';
for (int i=0;i<6;i++) {
int x=a,y=b; LL cd=cc;
for (int j=1;j<=i;j++) {
if (nex(x)==y) cd+=c[y],y=nex(y);
else if (nex(y)==x) cd+=c[x],x=nex(x);
}
if (nex(x)==y) cd+=c[x],x=y;
else if (nex(y)==x) cd+=c[y],y=x;
for (int j=0;j<6;j++) {
if (cd>cx) break;
if (x==u) {
LL mx=(cx-cd)/(c[0]+c[0]+c[1]+c[1]+c[2]+c[2]);
mx=getmod(mx);
ans+=(mx+1)*(mx+2)/2;
ans=getmod(ans);
}
cd+=c[x]; x=nex(x);
}
}
return ans;
}
void Solve() {
memset(a,0,sizeof(a));
scanf("%lld%lld%lld%lld",&c[0],&c[1],&c[2],&cx);
scanf("%s",s+1); scanf("%s",t+1);
len=strlen(s+1);
for (int i=1;i<=len;i++) {
a[s[i]-'x'][0]++; a[t[i]-'x'][1]++;
}
int cnt1=(a[0][0]>0)+(a[1][0]>0)+(a[2][0]>0);
int cnt2=(a[0][1]>0)+(a[1][1]>0)+(a[2][1]>0);
if (cnt1<cnt2) return (void) (puts("0"));
if (cnt1==1&&cnt2==1) return (void) (printf("%d\n",calc1(s,t,0)));
if (cnt1==2&&cnt2==2) return (void) (printf("%d\n",calc2(s,t,0)));
if (cnt1==3&&cnt2==3) return (void) (printf("%d\n",calc3(s,t)));
if (cnt1==3&&cnt2==2) {
LL ans=0;
for (int i=0;i<3;i++) {
for (int j=1;j<=len;j++) sx[j]=(s[j]==i+'x')?(nex(s[j]-'x')+'x'):s[j];
ans+=calc2(sx,t,c[i]);
}
return (void) (printf("%d\n",getmod(ans)));
}
if (cnt1==2&&cnt2==1) return (void) (printf("%d\n",calc4(s,t,0)));
if (cnt1==3&&cnt2==1) {
LL ans=0;
for (int i=0;i<3;i++) {
for (int j=1;j<=len;j++) sx[j]=(s[j]==i+'x')?(nex(s[j]-'x')+'x'):s[j];
ans+=calc4(sx,t,c[i]);
}
return (void) (printf("%d\n",getmod(ans)));
}
return (void) puts("0");
}
int main() {
int T; scanf("%d",&T);
while (T--) Solve();
return 0;
}
/*
2
2 5 3 12
xxx
yyy
1 1 2 2
zzz
yyy
2
1 1 1 10
xy
yz
1 1 1 10
xzz
xyx
2
1 1 1 10
xyz
xyz
1 1 1 10
xyz
yxz
2
1 1 1 10
xyz
xxy
1 1 1 10
xyz
yyx
3
1 1 1 5
xzx
xxx
1 1 1 10
xxy
xxx
1 1 1 20
xyx
zzz
1
3 2 2 9
xyz
xxx
*/
L. ZZX and Permutations
题意
给定一个长为n的全排列,用括号将它分为若干个区间,使变换后全排列的字典序最大。
例如全排列:1 4 5 6 3 2,如果划分为(1,4,5,6)(3,2),表示将1写到6的位置,6写到5的位置,5写到4的位置……
n<=100000
题解
从小到大考虑每个数的位置被哪个数所占据。这有三种情况:
1、被自己占据;
2、被左边可取范围内最大的数占据;
3、被右边第一个数占据。
对于前两种情况,我们可以直接确定一个括号区间。直接将它们写进答案后删除这个区间。注意左边最大数和这个数之前不能有被删除过的区间。
对于第三种情况,我们只能确定一个括号区间的左括号l。右括号的选择又有两种情况:
1、在后面考虑一个数x的时将[l,x]归为一个区间;
2、考虑(l+1)时确定右括号。
那么只需要让(l+1)不能为左括号就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,T;
int a[N],id[N],mx[N<<2],num[N<<2],ans[N],used[N];
set<int> Set;
void Init() {
scanf("%d",&n);
memset(a,0,sizeof(int)*(n+3));
memset(id,0,sizeof(int)*(n+3));
for (int i=1;i<=n;i++) scanf("%d",&a[i]),id[a[i]]=i;
Set.clear(); Set.insert(0);
memset(used,0,sizeof(int)*(n+3));
}
void Pushup(int x) {
if (mx[x<<1]<mx[x<<1|1]) mx[x]=mx[x<<1|1],num[x]=num[x<<1|1];
else mx[x]=mx[x<<1],num[x]=num[x<<1];
}
void Build(int x,int l,int r) {
if (l==r) return (void) (num[x]=l,mx[x]=a[l]);
int mid=(l+r)>>1;
Build(x<<1,l,mid); Build(x<<1|1,mid+1,r);
Pushup(x);
}
int Query(int x,int l,int r,int lt,int rt) {
if (rt<l||lt>r) return 0;
if (lt<=l&&r<=rt) return num[x];
int mid=(l+r)>>1;
int u=Query(x<<1,l,mid,lt,rt),v=Query(x<<1|1,mid+1,r,lt,rt);
return (a[u]<a[v])?v:u;
}
void Updata(int x,int l,int r,int pos) {
if (l==r) return (void) (num[x]=mx[x]=0);
int mid=(l+r)>>1;
if (pos<=mid) Updata(x<<1,l,mid,pos);
else Updata(x<<1|1,mid+1,r,pos);
Pushup(x);
}
void Solve() {
Init();
Build(1,1,n);
for (int i=1;i<=n;i++) {
if (used[id[i]]) continue;
int x=id[i];
set<int>::iterator it=Set.lower_bound(x);
int l=Query(1,1,n,*(--it)+1,x),r=x;
if (a[l]<a[r+1]&&!used[r+1]) {
ans[i]=a[r+1];
Updata(1,1,n,r+1);
}
else {
for (int i=l;i<=r;i++) {
ans[a[i]]=a[(i==r)?l:i+1];
Set.insert(i);
Updata(1,1,n,i);
used[i]=1;
}
}
}
for (int i=1;i<=n;i++) {
printf("%d",ans[i]);
putchar((i!=n)?' ':'\n');
}
memset(num,0,sizeof(int)*(2*n+10));
memset(mx,0,sizeof(int)*(2*n+10));
}
int main() {
scanf("%d",&T);
while (T--) Solve();
return 0;
}