排水系统
1.高精度+拓扑排序模板(细节还挺多的)
2.这道题用到了int128(会爆long long),int128是到2^127,long long是到1e18(分母 2 最高 22 次,3 和 5 最高 11 次,所以会爆了)
3..首先应该实现分数的加法,这里注意要随时除 gcd 来保持分母分子互质。已知了流水的方式,那么我们可以直接在图中进行模拟,可以用 dfs / bfs 来模拟流水过程,即枚举每一个污水接受口排出的水。分析一下就可以发现排水的过程其实就是一个拓扑排序的过程,每到一个点就把它所有出边的点都更新一下就好了。
#include<bits/stdc++.h>
#define ll __int128
using namespace std;
const int N=1e5+10,M=5e5+10;
int n,m;
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
ll lcm(ll a,ll b){
return a/gcd(a,b)*b;
}
struct node{
ll p,q;
node(){
p=0,q=1;
}
node operator *(const ll &rhs) const{
node res;
res.p=p,res.q=q*rhs;
ll g=gcd(res.p,res.q);
res.p/=g,res.q/=g;
return res;
}
node operator +(const node &rhs) const{
node res;
res.q=lcm(q, rhs.q);
res.p+=p*(res.q/q);
res.p+=rhs.p*(res.q/rhs.q);
ll g=gcd(res.p,res.q);
res.p/=g,res.q/=g;
return res;
}
}val[N];
struct Edge{
int to, next;
}e[M];
int head[N],ecnt,ind[N],d[N];
void addedge(int from, int to){
e[++ecnt]=(Edge){to,head[from]};
head[from]=ecnt;
ind[to]++;
}
queue<int> q;
vector<int> ans;
void toposort(){
for(int i=1;i<=n;i++)
if(ind[i] == 0) q.push(i);
while(!q.empty()){
const int x=q.front();
q.pop();
if(d[x]) val[x]=val[x]*d[x];
for(int i=head[x];i;i=e[i].next){
const int y=e[i].to;
val[y]=val[y]+val[x];
if(--ind[y]==0) q.push(y);
}
}
}
void print(ll n){
if(n>9) print(n/10);
putchar(n%10+48);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
if(i<=m) val[i].p=1;
scanf("%d",d+i);
if(d[i]==0) ans.push_back(i);
for(int j=1,v;j<=d[i];j++){
scanf("%d",&v);
addedge(i,v);
}
}
toposort();
for(int i=0;i<ans.size();i++){
print(val[ans[i]].p);
putchar(' ');
print(val[ans[i]].q);
putchar('\n');
}
return 0;
}
字符串匹配
1.本题要求 S=(AB)^iC 使得 A 中出现奇数次的字符数量 ≤ C 中出现奇数次的字符数量。
2.暴力思路:先枚举哪一段是 C,然后 hash 判前面循环节
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=1e6+10;
const ull base=130;
int t,n,c[26],cnt,sum[N][30];
ll ans;
string s;
ull h[N],power[N];
ull hs(int l,int r){
return h[r]-h[l-1]*power[r-l+1];
}
void pre(){
for(int i=1;i<=n;i++)
h[i]=h[i-1]*base+s[i];
for(int i=1;i<=n;i++){
int x=s[i]-'a';
c[x]++;
if(c[x]%2) cnt++;
else cnt--;
for(int j=0;j<=26;j++)
sum[i][j]=sum[i-1][j]+(cnt==j);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=26;j++)
sum[i][j]+=sum[i][j-1];
}
bool chk(int x,int len){
return hs(1,x-len)==hs(len+1,x);
}
int main(){
cin>>t;
power[0]=1;
for(int i=1;i<=N;i++) power[i]=power[i-1]*base;
while(t--){
memset(c,0,sizeof(c));
memset(sum,0,sizeof(sum));
cnt=0;
cin>>s;
n=s.size();
s=' '+s;
pre();
memset(c,0,sizeof(c));
cnt=0,ans=0;
for(int i=n;i>=3;i--){
int x=s[i]-'a';
c[x]++;
if(c[x]%2) cnt++;
else cnt--;
for(int len=1;len*len<i;len++){
if((i-1)%len==0){
if(chk(i-1,len)){
ans+=sum[len-1][cnt];
}
if(len*len<i-1&&chk(i-1,(i-1)/len)){
ans+=sum[(i-1)/len-1][cnt];
}
}
}
}
cout<<ans<<endl;
}
return 0;
}
3.可以想到一种做法:把 (AB) 结合为一个字符串,判断 (AB)^i 是否是 SS 的前缀,并用借助预处理统计答案。
(AB)^i 判断:想到 KMP中的 next 数组。可以想到一个结论:字符串A的最短周期 =∣A∣−next[A]
从二开始枚举 i,只要 S∣(AB)^i∣ 的最短周期是 ∣AB∣ 的约数,则 (AB)^i 一定是 S 的前缀。预处理出 next 数组即可
答案统计需要预处理出 S 每个后缀中出现奇数次字符数量即可确定 C 中出现奇数次的字符数量。统计答案同时更新到每一位时出现奇数次的字符数量 ≤x (x∈[0,26]) 的 A 的数量。
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef unsigned long long ull;
const ull base=131;
const int N=2e6+10;
int t,n,ans=0,vis[30],r[30],tot=0;
short a[N],b[N],c[N],sum[N],sum1[N];
ull v[N],l[N];
char s[N];
signed main(){
scanf("%lld",&t);
l[0]=1;
for(int i=1;i<=N;i++) l[i]=l[i-1]*base;
while(t--){
scanf("%s",s+1);
n=strlen(s+1);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(r,0,sizeof(r));
memset(vis,0,sizeof(vis));
memset(sum1,0,sizeof(sum1));
ans=0,tot=0;
for(int i=1;i<=n;i++){
c[i]=s[i]-'a'+1;
r[c[i]]^=1;
if(r[c[i]]) sum[i]=sum[i-1]+1;
else sum[i]=sum[i-1]-1;
a[i]=sum[i];
}
memset(r,0,sizeof(r));
for(int i=n;i>=1;i--){
r[c[i]]^=1;
if(r[c[i]]) sum1[i]=sum1[i+1]+1;
else sum1[i]=sum1[i+1]-1;
b[i]=sum1[i];
}
for(int i=1;i<=n;i++) v[i]=base*v[i-1]+1ull*c[i];
for(int i=2;i<=n;i++){
for(int j=a[i-1];j<=26;j++) vis[j]++;
for(int j=i;j<n&&v[i]==v[j]-v[j-i]*l[i];j+=i) ans+=vis[b[j+1]];
}
printf("%lld\n",ans);
}
return 0;
}
微信步数
1.题目挺难懂的哈(只会暴力)
2.称 n 步为一轮,首先 −1 的情况很好判断:一轮后回到原地且在第一轮里存在某个起点走不出去。把答案转换一下:原本是考虑每个起点各自走多少步出界,现在转换成同时考虑所有起点,把每天还 存活的起点 数量计入贡献。(这里存活就是指从该起点出发到某天还没出界)显然,只要把第 0 天活着的起点算进去(也就是 ∏wi),就和要算的答案等价了。一共 m 个维度,每个维度存活的位置是独立的,并且应是一段区间(只有开头、结尾的一部分会死亡)。
如果第 j 维存活的区间是 [lj,rj],那总共存活的数量就为∏ (rj−lj+1)。(j:1~m)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10,mod=1e9+7;
int w[20],e[20],l[20],r[20];
int c[N],d[N];
int n,m;
int main(){
scanf("%d%d",&n,&m);
ll ans=1;
for(int i=1;i<=m;i++){
scanf("%d",&w[i]);
ans=ans*w[i]%mod;
}
for(int i=1;i<=n;i++){
scanf("%d%d",&c[i],&d[i]);
}
while(1){
for(int i=1;i<=n;i++){
e[c[i]]+=d[i];
l[c[i]]=min(l[c[i]],e[c[i]]);
r[c[i]]=max(r[c[i]],e[c[i]]);
ll s=1;
for(int j=1;j<=m;j++){
if(r[j]-l[j]>=w[j]){
printf("%lld\n",ans);
return 0;
}
s=s*(w[j]-r[j]+l[j])%mod;
}
ans=(ans+s)%mod;
}
bool flag=1;
for(int j=1;j<=m;j++){
if(e[j]!=0) flag=0;
}
if(flag){
ans=-1;
break;
}
}
printf("%lld\n",ans);
return 0;
}
移球游戏
1.貌似是一道构造题?
2.用分治。每次假定只有两个颜色,达到的目的是每种颜色都在一边,接下来分两边递归处理。A 颜色和 B 颜色内部的顺序不用管,因为在接下来的递归中,它们都会被处理到。具体实现用一个 vector 处理就行了。
第一步,整理。即将每一列整理成上面 B 颜色,下面 A 颜色的状态。
第二步,我们使用合并操作。首先定义翻转操作。非常简单,将一列的数全部插入空列。
接着把两列的上面分别放进空集。把第二列的剩下的填满第一列。把空集里的东西丢回第二列。
这样每次都可以生成一个纯色列。结合这两大步,可以做完一次操作。
#include<bits/stdc++.h>
using namespace std;
const int N=60,M=410,K=820010;
int a[N][M],top[N],n,m,ans[K][2],tot;
bool flag[N];
inline void pour(int x,int y){
ans[++tot][0]=x,ans[tot][1]=y;
a[y][++top[y]]=a[x][top[x]--];
}
void solve(int l,int r){
if(l==r) return;
int mid=l+r>>1;
memset(flag,0,sizeof(flag));
for(int i=l;i<=mid;i++)
for(int j=mid+1;j<=r;j++){
if(flag[i] || flag[j]) continue;
int s=0;for(int k=1;k<=m;k++) s+=(a[i][k]<=mid);
for(int k=1;k<=m;k++) s+=(a[j][k]<=mid);
if(s>=m){
s=0;for(int k=1;k<=m;k++) s+=(a[i][k]<=mid);
for(int k=1;k<=s;k++) pour(j,n+1);
while(top[i]) a[i][top[i]]<=mid?pour(i,j):pour(i,n+1);
for(int k=1;k<=s;k++) pour(j,i);
for(int k=1;k<=m-s;k++) pour(n+1,i);
for(int k=1;k<=m-s;k++) pour(j,n+1);
for(int k=1;k<=m-s;k++) pour(i,j);
while(top[n+1]){
if(top[i]==m||a[n+1][top[n+1]]>mid) pour(n+1,j);
else pour(n+1,i);
}
flag[i]=1;
}
else{
s=0;
for(int k=1;k<=m;k++) s+=(a[j][k]>mid);
for(int k=1;k<=s;k++) pour(i,n+1);
while(top[j]) a[j][top[j]]>mid?pour(j,i):pour(j,n+1);
for(int k=1;k<=s;k++) pour(i,j);
for(int k=1;k<=m-s;k++) pour(n+1,j);
for(int k=1;k<=m-s;k++) pour(i,n+1);
for(int k=1;k<=m-s;k++) pour(j,i);
while(top[n+1]){
if(top[j]==m||a[n+1][top[n+1]]<=mid) pour(n+1,i);
else pour(n+1,j);
}
flag[j]=1;
}
}
solve(l,mid),solve(mid+1,r);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++)
scanf("%d",&x),a[i][++top[i]]=x;
solve(1,n);
printf("%d\n",tot);
for(int i=1;i<=tot;i++) printf("%d %d\n",ans[i][0],ans[i][1]);
return 0;
}
总之前两道题需要注意的细节挺多的,后两道题较难,题目也比较难理解,尽力打打暴力