大假期集训模拟赛11
解方程
题目描述
解出一元二次方程 \(ax+by=c\) 的一组整数解 \((x0, y0)\) ,使 \(\left |x_0+y_0 \right |\) 最小。
输入格式
共一行,三个整数 \(a\) , \(b\) , \(c\) 。
输出格式
共一行,为 \(\left |x_0+y_0 \right |\) 的最小值。
若无解输出 \(“kito”\) 。
样例
样例输入 #1
1 1 1
样例输出 #1
1
样例输入 #2
2 3 1
样例输出 #2
0
数据范围与提示
有 \(30\%\) 的数据,\(a,b\) 均为质数,\(c=1\) 。
另有 \(20\%\) 的数据,\(a,b,c\) 均为质数。
\(100\%\) 的数据,\(a,b,c\leq 1,000,000,000\) 。
思路
当时考试以为是个大数论,直接跳过,拿了特判 \(10opts\) 走人。
其实思路很简单,\(x,y\) 只能是整数,推一下式子就行了。
设\(\left |x_0+y_0 \right |=i\)
\(\therefore x=i-y\) 或 \(x=-i-y\)
\(\therefore y=\frac {c\pm a*i} {b-a}\)
这样只需枚举 \(i\) ,并判断求出来的 \(y\) 是不是整数即可。
注意要特判一下 \(b==a\) ,不然会出错。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e9+50;
inline int read(){
int x=0,w=1;
char ch;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int a,b,c;
signed main(){
a=read(),b=read(),c=read();
if(c==0){//加这个会更快一丢丢
printf("0\n");
return 0;
}
if(a==b){//特判a==b
if(c%a==0){
printf("%lld\n",c/a);
}else{
printf("kito\n");
}
return 0;
}
for(int i=0;i<=maxn;i++){//枚举i
if((c-a*i)%(b-a)==0){
printf("%lld\n",i);
return 0;
}
if((c+a*i)%(b-a)==0){
printf("%lld\n",i);
return 0;
}
}
printf("kito\n");
return 0;
}
最佳序列
题目描述
你得到了一个 \(N\) 个非负整数组成的序列 \(A\) 。
我们称由序列 \(A\) 的连续若干项组成的新序列为 \(A\) 的一个连续子序列。给出两个正整数 \(L,R(L\leq R)\) 。称 \(A\) 的每一个长度不小于 \(L\) 且不大于 \(R\) 的连续子序列为一个纯洁序列,定义纯洁度为纯洁序列的所有元素的平均值。
请你求出所有纯洁序列中的纯洁度的最大值。
输入格式
输入文件的第一行包括 \(3\) 个正整数 \(N,L,R\) 。
第二行包括 \(N\) 个数,按顺序依次表示序列 \(A\) 的每一项。
输出格式
输出文件包括一行,一个实数,保留 \(4\) 位小数,表示答案。
样例
样例输入
3 2 3
6 2 8
样例输出
5.3333
数据范围与提示
\(20\%\) 的数据满足,\(1\leq N\leq 200\) 。
\(40\%\) 的数据满足,\(1\leq N\leq 2,000\) 。
\(100\%\) 的数据满足,\(1\leq N\leq 20,000\) , \(1\leq L\leq R\leq N\) , \(0\leq A_i\leq 1,000,000\) 。
思路
当时考试的时候想出了 \(3\) 种方法:
-
\(n^3\)的暴力,\(20,000\) 肯定会爆。
-
在上面的基础上用前缀和优化一下,直接压到 \(n^2\) 。
-
用单调队列来维护,二分查找答案,直接压倒 \(nlog_n\),
但是我考试的时候写炸了。
代码
法二暴力
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e4+50;
inline int read(){
int x=0,w=1;
char ch;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int n,l,r;
int a[maxn],sum[maxn];
double ans;
int main(){
n=read(),l=read(),r=read();
for(register int i=1;i<=n;i++){
a[i]=read();
sum[i]=sum[i-1]+a[i];
}
for(register int d=l;d<=r;d++){//register优化一下
int now=0;
for(register int i=1,j;(j=i+d-1)<=n;i++){
now=max(now,sum[j]-sum[i-1]);//除法会比较慢,所以求出和的最大,再进行除法,会很快
}
ans=max(ans,(double)now/d);
}
printf("%.4lf\n",ans);
return 0;
}
法三(借用一手大佬的码)
#include <bits/stdc++.h>
using namespace std;
const int maxn=20000+10;
int l,r,n;
int a[maxn];
double tmp[maxn];
double sum[maxn];
int qu[maxn];
bool pd(double x){
for(int i=1;i<=n;i++){
tmp[i]=(double)a[i]-x;
sum[i]=sum[i-1]+tmp[i];
}
int head=1,tail=0;
int now=0;
double ans=-1e9;
for(int i=1;i<=n;i++){
while(i-now>=l){
while(head<=tail&&sum[qu[tail]]>=sum[now]){
tail--;
}
qu[++tail]=now;
now++;
}
while(head<=tail&&i-qu[head]>r){
head++;
}
if(head<=tail){
ans=max(ans,sum[i]-sum[qu[head]]);
}
}
return ans>=0;
}
int main(){
scanf("%d%d%d",&n,&l,&r);
double fl=0,fr;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
fr=max(fr,(double)a[i]);
}
while(fr-fl>=1e-5){
double mid=(fl+fr)/2;
if(pd(mid)){
fl=mid;
}else{
fr=mid;
}
}
printf("%.4lf",(fl+fr)/2);
return 0;
}
周期串查询
题目描述
有一个串只包含数字字符。串的长度为 \(n\) ,下标从 \(1\) 开始。
有两种操作方式:
\(1,l,r,c\) (\(1\leq l\leq r\leq n\) , \(c\) 是数字字符),表示将 \(l\) 到 \(r\) 这一段的字符全部改成c字符;
\(2,l,r,d\) (\(1\leq l\leq r\leq n\) , \(1\leq d\leq r-l+1\)),表示询问 \(l\) 到 \(r\) 这一段区间内的子串是否是以d为周期的串。
字符串 \(s\) 是以 \(x\) \((1\leq x\leq \left |s \right |)\) ,为周期的串的条件是:对于所有的 \(i\) 从 \(1\) 到\(\left |s \right |-x\) , \(s_i = s_i + x\) 都成立。
输入格式
单组测试数据。
第一行有两个整数\(n, m ,k (1\leq n\leq 10^5, 1\leq m+k\leq 10^5)\),表示字符串长度和修改次数和询问次数。
第二行给出原始的串包含 \(n\) 位数字字符。
接下来 \(m+k\) 行,每行一个操作。
有两种形式:
\(1,l,r,с\) (\(1\leq l\leq r\leq n\) , \(c\) 是数字字符);
\(2,l,r,d\) (\(1\leq l\leq r\leq n\) , \(1\leq d\leq r-l+1\))。
输出格式
对于每一个询问,如果该段子串是以 \(d\) 为周期的,输出 \(YES\) ,否则输出 \(NO\) 。
样例
样例输入
3 1 2
112
2 2 3 1
1 1 3 8
2 1 2 1
样例输出
NO
YES
数据范围与提示
在这个样例中第一个询问的时候子串是“ \(12\) ”,他不是以 \(1\) 为周期的,所以答案是 \(NO\) 。第二个询问的时候子串是“ \(88\) ”,是以 \(1\) 为周期的,所以答案是 \(YES\) 。
思路
\(hash\) 线段树的老板子了,但是 \(memset+memcmp\) 也能卡过,而且还不好卡,正解线段树反而更慢。
代码
memset+memcmp
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+50,INF=0x3f3f3f3f;
inline int read(){
int x=0,w=1;
char ch;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int n,m,k;
char a[maxn];
int main(){
n=read(),m=read(),k=read();
scanf("%s",a+1);
for(int i=1;i<=m+k;i++){
int opt=read(),l=read(),r=read(),x=read();
if(opt==1){
memset(a+l,x+'0',r-l+1);
}else{
if(memcmp(a+l,a+l+x,r-l-x+1)){
puts("NO");
}else{
puts("YES");
}
}
}
return 0;
}
hash
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int maxn=2e7+50,mod=1e9+9,base=17;
inline int read(){
int x=0,w=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int n,m;
char s[maxn];
int a[maxn],b[maxn];
struct Node{
int l,r,lazy;
int hash;
}node[maxn];
void Pushup(int x){
node[x].hash=((node[x<<1].hash*a[node[x<<1|1].r-node[x<<1|1].l+1]+node[x<<1|1].hash)%mod+mod)%mod;
}
void Pushdown(int x){
if(node[x].lazy+1){
node[x<<1].lazy=node[x<<1|1].lazy=node[x].lazy;
node[x<<1].hash=b[node[x<<1].r-node[x<<1].l]*node[x].lazy%mod;
node[x<<1|1].hash=b[node[x<<1|1].r-node[x<<1|1].l]*node[x].lazy%mod;
node[x].lazy=-1;
}
return;
}
void Build(int rt,int l,int r){
node[rt].l=l;
node[rt].r=r;
node[rt].lazy=-1;
if(l==r){
node[rt].hash=s[l]-'0'+1;
return;
}
int mid=(node[rt].l+node[rt].r)>>1;
Build(rt<<1,l,mid);
Build(rt<<1|1,mid+1,r);
Pushup(rt);
}
void Add(int rt,int l,int r,int w){
if(node[rt].l==l&&node[rt].r==r){
node[rt].lazy=w;
node[rt].hash=b[node[rt].r-node[rt].l]*w%mod;
return;
}
Pushdown(rt);
int mid=(node[rt].l+node[rt].r)>>1;
if(mid>=r){
Add(rt<<1,l,r,w);
}else if(l>mid){
Add(rt<<1|1,l,r,w);
}else{
Add(rt<<1,l,mid,w);
Add(rt<<1|1,mid+1,r,w);
}
Pushup(rt);
}
int Query(int rt,int l,int r){
if(l>r)return 0;
if(node[rt].l==l&&node[rt].r==r){
return node[rt].hash;
}
Pushdown(rt);
int mid=(node[rt].l+node[rt].r)>>1;
if(mid>=r){
return Query(rt<<1,l,r);
}else if(mid<l){
return Query(rt<<1|1,l,r);
}else {
return (Query(rt<<1,l,mid)*a[r-mid]+Query(rt<<1|1,mid+1,r))%mod;
}
}
signed main(){
n=read();m=read();
scanf("%s",s+1);
a[0]=1;
b[0]=1;
for(int i=1;i<=n;i++){
a[i]=a[i-1]*base%mod;
b[i]=(b[i-1]*base+1)%mod;
}
Build(1,1,n);
while(m--){
int pd=read(),l=read(),r=read(),x=read();
if(pd==1){
Add(1,l,r,x+1);
}else{
if(Query(1,l,r-x)==Query(1,l+x,r)){
printf("YES\n");
}else{
printf("NO\n");
}
}
}
return 0;
}
追捕
题目描述
\(Duan2baka\) :“\(jmsyzsfq\) 天下第一蠢!”
\(jmsyzsfq\) :“你说什么?!”
于是 \(Duan2baka\) 开始了逃亡的旅程,而 \(jmsyzsfq\) 也开始追捕 \(Duan2baka\) 。
他们来到了一个有 \(n\) 个节点的有向图,迅速逃跑的 \(Duan2baka\) 会首先降落在有向图的某个节点T上,因为怕发出声音,他会永远静止不动。而随后跟来的 \(jmsyzsfq\) 会降落在图上随机一个节点 \(S\) 上(可以与 \(T\) 相同),并可以沿着图中的有向边随意走动。
因为 \(jmsyzsfq\) 有着敏锐的嗅觉而且绝顶聪明,他一定会沿着正确的方向寻找 \(Duan2baka\) 。你可以认为,如果图中存在一条合法的从 \(S\) 到 \(T\) 的路径,那么 \(jmsyzsfq\) 一定会抓到 \(Duan2baka\) ——因此 \(jmsyzsfq\) 能否追捕到 \(Duan2baka\) 在他刚刚降落在图上的时候就已经确定了。
现在请你帮帮 \(jmsyzsfq\) ,在他降落前告诉他有多大的概率能够追捕到 \(Duan2baka\) ,并告诉他想要追到 \(Duan2baka\) 他可以降落在哪些节点上。
输入格式
输入第一行包含三个整数\(n\) , \(m\) , \(opt\) ,表示图中有 \(n\) 个节点,\(m\) 条有向边;\(opt\) 为 \(0\) 或 \(1\) ,含义见输出格式所述。
输入第 \(2\) 至 \(m+1\) 行每行两个整数 \(u\) ,\(v\) 描述了图中一条从 \(u\) 到 \(v\) 的有向边。
输入第 \(m+2\) 行一个整数 \(T\) 表示 \(Duan2baka\) 降落的位置。
输出格式
如果输入的 \(opt\) 为 \(0\) ,只需要输出 \(jmsyzsfq\) 能够追捕到 \(Duan2baka\) 的概率。
如果输入的 \(opt\) 为 \(1\) ,输出两行,第一行输出 \(jmsyzsfq\) 能够追捕到 \(Duan2baka\) 的概率;第二行按从小到大输出想要追到 \(Duan2baka\) 他可以降落的节点编号,编号间用空格隔开。
对于 \(jmsyzsfq\) 能够追捕到 \(Duan2baka\) 的概率,是一个既约分数,形如“ \(a/b\) ”(\(a,b\) 为整数),使 \(gcd(a,b)=1\) ,如果 \(a=b\) ,输出 \(1/1\) 。
样例
样例输入 #1
9 10 1
1 2
2 1
2 4
6 1
9 6
6 5
5 3
3 7
3 1
1 8
1
样例输出 #1
2/3
1 2 3 5 6 9
样例 #1解释
图中共 \(9\) 个节点,能够到达节点 \(S=1\) 的节点共 \(6\) 个:\({1,2,3,5,6,9}\) 。所以能够追捕到 \(Duan2baka\) 的概率为 \(6/9=2/3\) 。
样例输入 #2
5 7 1
1 2
1 3
1 5
2 4
4 1
4 5
5 3
1
样例输出 #2
3/5
1 2 4
数据范围与提示
\(opt=0\) 和 \(opt=1\) 的数据各 \(50\%\) 。
对于 \(opt=0\) 和 \(opt=1\) 的情况,有着完全相同的子任务:
对于 \(10\%\) 的数据,\(n,m\leq 100\) 。
对于另外 \(30\%\) 的数据,\(n,m\leq 100,000\) 。
对于另外 \(10%\) 的数据,保证图是一个完全图,即对于任意两个点 \(u\) 和 \(v\) ,必然有两条有向边 \(u→v\) 和 \(v→u\) 。
对于另外 \(20\%\) 的数据,保证图是一个有向无环图,即对于任意一个点 \(x\) ,不存在图上一条路径从 \(x\) 经过其它点后回到 \(x\) 。
对于另外 \(20%\) 的数据,保证如果图上存在一条边 \(u→v\) ,一定存在一条边 \(v→u\) 。
对于 \(100\%\) 的数据,\(n,m\leq 1,000,000\) ,保证数据合法且不存在重边、自环。
思路
裸的 \(DFS\) ,根本没有思路可言,反向建边,反向跑一遍 \(DFS\) ,这不是有手就行???
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+50,INF=0x3f3f3f3f;
inline int read(){
int x=0,w=1;
char ch;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int n,m,opt,s;
int ans;
int vis[maxn];
int path[maxn];
struct Edge{
int to,next;
}e[maxn];
int GCD(int a,int b){
return b==0 ? a : GCD(b,a%b);
}
int tot,head[maxn];
void Add(int u,int v){
e[++tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
void DFS(int u){
vis[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(vis[v])continue;
vis[v]=1;
DFS(v);
}
}
int main(){
n=read(),m=read(),opt=read();
for(register int i=1;i<=m;i++){
int u=read(),v=read();
Add(v,u);//反向建边
}
s=read();
DFS(s);//从终点往其他方向跑
for(int i=1;i<=n;i++){
if(vis[i]){
path[++ans]=i;
}
}
int chu=GCD(ans,n);
if(chu!=1){
printf("%d/%d",ans/chu,n/chu);
}else{
printf("%d/%d",ans,n);
}
if(opt==1){
printf("\n");
for(register int i=1;i<=ans;i++){
printf("%d ",path[i]);
}
printf("\n");
}
return 0;
}
不等式
题目描述
小 \(z\) 热衷于数学。
今天数学课的内容是解不等式: \(L\leq S\times x\leq R\) 。
小z心想这也太简单了,不禁陷入了深深的思考:假如已知 \(L,R,S,M\) ,满足 \(L\leq (S\times x)mod M\leq R\) 的最小正整数该怎么求呢?
输入格式
第一行包含一个整数 \(T\) ,表示数据组数,接下来是 \(T\) 行,每行为四个正整数 \(M,S,L,R\) 。
输出格式
对于每组数据,输出满足要求的 \(x\) 值,若不存在,输出 \(-1\) 。
样例
样例输入
1
5 4 2 3
样例输出
2
数据范围与提示
\(30\%\) 的数据中保证有解并且答案小于等于 \(10^6\);
另外 \(20\%\) 的数据中保证 \(L=R\);
\(100\%\) 的数据中 \(T\leq 100,M,S,L,R\leq 10^9\)。
思路
首先我们将原式 \(L\leq (S\times x)mod M\leq R\) 转化成两种式子:
- \(L\leq S\times x\leq R\)
直接求解左边的最小值,即\(x=\left \lceil \frac{L}{S} \right \rceil\),判断一下是否满足 \(S\times x\leq R\) ,否则无解。
- \(L\leq S\times x-M\times y\leq R\)(\(y\) 为新的未知数)
移项可得 \(S\times x-R\leq M\times y\leq S\times x-L\)
很明显 \(x\) 和 \(y\) 是同增同减的,所以将 \(y\) 设为我们的主元(主要要求的未知数)。
式子可以变成:
\(-R \:\ mod \:\ S\leq M\times y \:\ mod \:\ S\leq -L \:\ mod \:\ S\) (\(L\) 和 \(R\) 会在一个 \(S\) 范围内,所以可以直接取模)
再定义:
\(L'=-R \:\ mod \:\ S,R'=-L \:\ mod \:\ S\)
\(S'=M \:\ mod \:\ S,M'=S\)
然后我们再递归求解:
- \(L+M\times y\leq S\times x\leq R+M\times y\)
当两端点很接近时,会有:
\(x=\left \lceil \frac{L+M\times y}{S} \right \rceil=\left \lfloor \frac{R+M\times y}{S} \right \rfloor\)
所以当 \(y\) 存在时,就会存在一个解 \(x\) 。
所以我们每次递归的时候,将区间缩小:
原区间为 \([L,R]\) ,宽度为 \(R-L+1\) 。
缩小后的区间为 \([L',R']\) ,宽度为:
\(R'-L'+1=(-L\%S+S)\%S-(-R\%S+S)\%S+1=(R-L)\%S+1\leq R-L+1\)
直到区间小到只有唯一解时,并且在满足没有超出区间范围的情况下,求出唯一解。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,w=1;
char ch;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
int T;
int m,s,l,r;
int Solve(int m,int s,int l,int r){
if(r>=m) r=m-1;//防止取模后为0
if(l>r||m<=l) return -1;//无解的情况
s%=m;
int ans=(l-1)/s+1;//求出第一种情况的解
if(ans*s<=r){//没有超出右端点
return ans;
}
int a=(-r%s+s)%s;//缩小范围
int b=(-l%s+s)%s;
int y=Solve(s,m,a,b);//向下递归
if(y==-1)return -1;//无解
int x=(r+m*y)/s;//其实用左边求值,判断是否超出右边界,也是一样
if(l<=s*x-m*y){
return x;
}
return -1;//剩下都是无解
}
signed main(){
T=read();
while(T--){
m=read(),s=read(),l=read(),r=read();
printf("%lld\n",Solve(m,s,l,r));
}
return 0;
}