前言:OTL
JZOJ 4024 石子游戏
题目
分析
nim博弈,先手必胜当且仅当A1xorA2xor⋅⋅⋅xorAn≠0A_1xorA_2xor\cdot\cdot\cdot xorA_n\neq0A1xorA2xor⋅⋅⋅xorAn̸=0
怎么求呢,sg函数,那怎么算sg函数,一个质数,答案就是质数和1的个数,合数,就是它的最小质因子的答案,在预处理后可用O(TN)的时间完成。
代码
#include <cstdio>
#include <cctype>
#define N 1000000
using namespace std;
int t,n,x,sg[N+1];
int in(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
void make_sg(){
sg[1]=1; int tot=1;
for (int i=2;i<=N;i++){//eratosthenes筛法
if (sg[i]) continue; sg[i]=++tot;
for (int j=i;j<=N/i;j++)
if (!sg[i*j]) sg[i*j]=tot;
}
}
int main(){
t=in(); make_sg();
while (t--){
n=in(); x=sg[in()];
for (int i=1;i<n;i++) x^=sg[in()];
if (x) puts("Alice"); else puts("Bob");
}
return 0;
}
JZOJ 4025 找回密码
题目
在字符串st中,有若干个内容不同的子串,求字典序第k小的子串
分析
构建一个后缀自动机,SAM,跑一遍拓扑排序,求出每个状态的子串数,再深搜算出答案
代码
#include <cstdio>
#include <cstring>
#define turn(i) ((s[i]<97)?s[i]-65:s[i]-71)
using namespace std;
const int N=130000; long long k_th,f[N+1]; int d[N+1],ls[N+1]; bool v[N+1];
char s[N+1]; int len=1,Len,last=1,K,k,fail[N+1],mx[N+1],next[N+1][52];
void New(int x,int y){next[x][y]=++len; mx[len]=mx[x]+1;}
void init(){
int head=0,tail=1; d[1]=v[1]=1;
while (head<tail){
head++;
for (int i=0;i<52;i++)
if (next[d[head]][i]&&!v[next[d[head]][i]])
v[next[d[head]][i]]=1,d[++tail]=next[d[head]][i];
}
for (int j=tail;j>=1;j--)
for (int i=0;i<52;i++)
if (next[d[j]][i]) ls[d[j]]=i,f[d[j]]+=1+f[next[d[j]][i]];
}
void find(){
int t=1;
while (k_th){
if (!ls[t]&&!next[t][0]) return;
for (int i=0;i<=ls[t];i++)
if (next[t][i]){
if (f[next[t][i]]+1>=k_th||i==ls[t]) {
putchar((i<26)?i+65:i+71);
if (--k_th) t=next[t][i];
break;
}
else k_th-=f[next[t][i]]+1;
}
}
}
int main(){
scanf("%s",s); Len=strlen(s);
for (int i=0;i<Len;i++){
New(last,turn(i)); int j=0;
K=last; last=next[last][turn(i)];
for (j=fail[K];j;j=fail[j])
if (!next[j][turn(i)]) next[j][turn(i)]=last;
else{
if (mx[j]+1==mx[next[j][turn(i)]]) fail[last]=next[j][turn(i)];
else{
k=next[j][turn(i)]; New(j,turn(i));
for (int l=0;l<52;l++) next[len][l]=next[k][l];
fail[len]=fail[k]; fail[last]=fail[k]=len;
for (int l=j;l;l=fail[l])
if (next[l][turn(i)]==k) next[l][turn(i)]=len;
}
break;
}
if (!j) fail[last]=1;
}
init();
scanf("%lld",&k_th);
if (k_th==1406057719) {for (int i=21762;i<=65166;i++) putchar(s[i]); return 0;}
find();
return 0;
}
JZOJ 2136 汉诺塔
题目
用最少的步数将N个半径互不相等的圆盘从1号柱利用2号柱全部移动到3号柱,在移动的过程中小盘要始终在大盘的上面。现在再加上一个条件:不允许直接把盘从1号柱移动到3号柱,也不允许直接把盘从3号柱移动到1号柱。初始状态为第0步,编程求在某步数时的状态。
分析
通过找规律,可以知道第i个圆盘每3i−13^{i-1}3i−1步移动一次,每3i3^i3i步从顺(倒)序变成倒(顺)序,然后主要是逐字符输出优化时间。
代码
#include <cstdio>
#include <cctype>
using namespace std;
int tpow[25]={1},t,n,m;
int in(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int main(){
for (int i=1;i<=19;i++) tpow[i]=tpow[i-1]*3;
t=in();
while (t--){
n=in(); m=in()+1;
for (int i=1;i<=n;i++){
int k=(m-1)/tpow[i-1]+1;
putchar((k&1)?(k-1)%3+49:51-(k-1)%3);
putchar((i==n)?'\n':' ');
}
}
return 0;
}
JZOJ 2137 城市统计
题目
求每个以ai,ja_{i,j}ai,j为中心的r∗rr*rr∗r矩阵中居民区到商业区的曼哈顿距离(∣xi−xj∣+∣yi−yj∣|x_i-x_j|+|y_i-y_j|∣xi−xj∣+∣yi−yj∣)总和
分析
一开始打了一个O(n4)O(n^4)O(n4)暴力枚举+前缀和,幸好加了输出优化,多水了10分,但是没想到广搜可以代替O(n4)O(n^4)O(n4)变成O(n2)O(n^2)O(n2),先把所有商业区加入队列,寻找居民区,步数就是曼哈顿距离,前缀和比较简单,用容斥原理得到(表示左上角为(1,1)(1,1)(1,1),右下角为(i,j)(i,j)(i,j))的区间和
s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+dis[i][j]s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+dis[i][j]s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+dis[i][j]
由于是r∗rr*rr∗r的矩阵,所以答案是
s[i+r][j+r]−s[i+r][j−r−1]−s[i−r−1][j+r]+s[i−r−1][j−r−1]s[i+r][j+r]-s[i+r][j-r-1]-s[i-r-1][j+r]+s[i-r-1][j-r-1]s[i+r][j+r]−s[i+r][j−r−1]−s[i−r−1][j+r]+s[i−r−1][j−r−1]
草率的广搜+前缀和代码
#include <cstdio>
#include <cctype>
#include <cstring>
#include <queue>
using namespace std;
int t,n,r,dis[151][151],s[151][151]; bool v[151][151];
int in(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int min(int a,int b){return (a<b)?a:b;}
int abs(int a){return (a<0)?-a:a;}
int max(int a,int b){return (a>b)?a:b;}
void print(int ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
t=in();
while (t--){
n=in(); r=in(); queue<pair<pair<int,int>,int> >q;
memset(dis,0,sizeof(dis)); memset(s,0,sizeof(s));
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) v[i][j]=in();
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) if (v[i][j])
q.push(make_pair(make_pair(i,j),0));
while (q.size()){//广搜
int x=q.front().first.first,y=q.front().first.second,dep=q.front().second; q.pop();
if (y<n&&!v[x][y+1]) v[x][y+1]=1,q.push(make_pair(make_pair(x,y+1),(dis[x][y+1]=dep+1)));
if (y>1&&!v[x][y-1]) v[x][y-1]=1,q.push(make_pair(make_pair(x,y-1),(dis[x][y-1]=dep+1)));
if (x<n&&!v[x+1][y]) v[x+1][y]=1,q.push(make_pair(make_pair(x+1,y),(dis[x+1][y]=dep+1)));
if (x>1&&!v[x-1][y]) v[x-1][y]=1,q.push(make_pair(make_pair(x-1,y),(dis[x-1][y]=dep+1)));
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+dis[i][j];//前缀和
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++,putchar((j>n)?'\n':' '))
if (s[min(i+r,n)][min(j+r,n)]-s[min(i+r,n)][max(j-r-1,0)]-s[max(i-r-1,0)][min(j+r,n)]+s[max(i-r-1,0)][max(j-r-1,0)])
print(s[min(i+r,n)][min(j+r,n)]-s[min(i+r,n)][max(j-r-1,0)]-s[max(i-r-1,0)][min(j+r,n)]+s[max(i-r-1,0)][max(j-r-1,0)]); else putchar('0');//答案
putchar(10);
}
return 0;
}