1001 fraction
题意是求
b
b
b使得
a
b
≡
x
(
m
o
d
\frac{a}{b} \equiv x (mod
ba≡x(mod
p
)
p)
p)
引入一个数
y
y
y变形成
a
=
b
x
−
p
y
a=bx-py
a=bx−py
考虑到
a
a
a的范围是
0
<
a
<
b
0<a<b
0<a<b,把上式代入就可以得到
p
x
<
b
y
<
p
x
−
1
\frac{p}{x}<\frac{b}{y}<\frac{p}{x-1}
xp<yb<x−1p,然后求一个最小的
b
b
b
这是一个经典的问题(然而我并没有见过)
如果这个区间跨越了一个整数
d
d
d(且是最小的),那么
b
y
=
d
1
\frac{b}{y}=\frac{d}{1}
yb=1d,因为
b
b
b不可能再小了
否则,不等式通通减去
d
−
1
d-1
d−1,这个时候两边的数应该都是小于1的,取个倒数继续递归就好了
for example:11 7
(
11
7
,
11
6
)
=
>
(
4
7
,
5
6
)
=
>
(
6
5
,
7
4
)
=
>
(
1
5
,
3
4
)
=
>
(
4
3
,
5
1
)
=
>
s
o
l
v
e
d
(\frac{11}{7},\frac{11}{6})=>(\frac{4}{7},\frac{5}{6})=>(\frac{6}{5},\frac{7}{4})=>(\frac{1}{5},\frac{3}{4})=>(\frac{4}{3},\frac{5}{1})=>solved
(711,611)=>(74,65)=>(56,47)=>(51,43)=>(34,15)=>solved
类似于辗转相除法,然后再反着套回去就好了
#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 200005
#define ll long long
using namespace std;
int T;
ll p,x,y,b;
void solve(ll a,ll b,ll &x,ll &y,ll c,ll d)
{
//cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;
ll p = a / b;
if (a % b) p++;
if (c / d >= p) {x = p; y = 1; return;}
a = a - b * (p - 1);
c = c - d * (p - 1);
solve(d,c,y,x,b,a);
x = x + y * (p - 1);
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%lld%lld",&p,&x);
solve(p,x,b,y,p,x-1);
printf("%lld/%lld\n",b*x-p*y,b);
}
return 0;
}
1002 three arrays
很好奇这个题为什么这么多人a掉了。。
首先一个显然的贪心是,每次找到最小的一对取出来,删掉这一对后继续做
一开始想了一个比较暴力的做法,就是维护每个
a
i
a_i
ai对应的最小的
b
i
b_i
bi,每次去掉一对后要更新所有的对应关系
感觉对于随机数据没啥问题?后来想想应该可以构造一组数据使得需要大量更新对应关系
(然后成功猜中被出题人公开处刑的错误做法)
(其实距离正解挺近了)
正解的做法是,不断找到对应的最小值:
a
1
=
>
b
1
=
>
a
2
=
>
b
2
=
>
.
.
.
.
a_1=>b_1=>a_2=>b_2=>....
a1=>b1=>a2=>b2=>....
然后最后一定能回到前一个值,比如
b
2
b_2
b2对应的最小值如果已经出现过,那么一定是
a
2
a_2
a2
所以每个元素出入栈各一次,也只会在字典树上跑一次,复杂度可以接受
然后简单证明一下这个结论的正确性:
结论1:
如
果
a
x
对
应
b
y
如果a_x对应b_y
如果ax对应by,
b
y
也
对
应
a
x
b_y也对应a_x
by也对应ax,那么这一对一定是答案
证明:如果不是,那么通过交换成上述情况一定更优
结论2:把上述对应关系看作是单向边,则不存在长度大于2的环(也就是前面说的一定能回到前一个值)
证明:
x
−
>
y
−
>
z
x->y->z
x−>y−>z可以得出x xor y大于y xor z(否则
y
−
>
x
y->x
y−>x),然后一直绕绕绕最后就会绕回
x
−
>
y
x->y
x−>y,
x
y
xy
xy匹配大于
x
y
xy
xy,矛盾,所以最多只会出现两个元素的环
然后呢我一开始使用递归的写法写的,有点难处理数字重复的情况,用queue
和stack
疯狂mle,用list
和vector
就开始tle。。常数是真的大
最后改了很多常数才刚好卡进去
后来发现读入优化挺慢的。。
#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 200005
using namespace std;
int tot[2],ls[N*31][2],rs[N*31][2],lnum[N*31][2],rnum[N*31][2],root[2];
int a[N],b[N],res[N],stck[N],bits[N];
int T,n,i,pt,resnum,x,y,temp;
void insert(int v,int op)
{
int pos = 1; int i;
fd(i,29,0)
{
int bs = (v >> i) & 1;
if (bs == 0)
{
if (ls[pos][op] == 0) ls[pos][op] = ++tot[op];
lnum[pos][op]++;
pos = ls[pos][op];
} else
{
if (rs[pos][op] == 0) rs[pos][op] = ++tot[op];
rnum[pos][op]++;
pos = rs[pos][op];
}
}
}
int find(int v,int op)
{
int pos = 1; int res = 0; int i;
fd(i,29,0)
{
int bs = (v >> i) & 1;
if (bs == 0)
{
if (lnum[pos][op] > 0) pos = ls[pos][op];
else if (rnum[pos][op] > 0) {pos = rs[pos][op]; res += 1 << i;}
else return -1;
} else
{
if (rnum[pos][op] > 0) {pos = rs[pos][op]; res += 1<< i;}
else if (lnum[pos][op] > 0) pos = ls[pos][op];
else return -1;
}
}
return res;
}
void del(int v,int op)
{
int pos = 1; int i;
fd(i,29,0)
{
int bs = (v >> i) & 1;
if (bs == 0)
{
if (lnum[pos][op] > 0) {lnum[pos][op]--; pos = ls[pos][op];}
else {rnum[pos][op]--; pos = rs[pos][op];}
} else
{
if (rnum[pos][op] > 0) {rnum[pos][op]--; pos = rs[pos][op];}
else {lnum[pos][op]--; pos = ls[pos][op];}
}
}
}
#include<cctype>
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main()
{
scanf("%d",&T);
//T=read();
while (T--)
{
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);
fo(i,1,n) scanf("%d",&b[i]);
//n=read();
fo(i,1,n*31) lnum[i][0] = lnum[i][1] = rnum[i][0] = rnum[i][1] = 0;
fo(i,1,n*31) ls[i][0] = ls[i][1] = rs[i][0] = rs[i][1] = 0;
tot[0] = tot[1] = 1;
//fo(i,1,n) a[i]=read();
//fo(i,1,n) b[i]=read();
fo(i,1,n) insert(a[i],0);
fo(i,1,n) insert(b[i],1);
pt = 0; resnum = 0;
fo(i,1,n)
{
int tt = find(0,0);
if (tt == -1) break;
pt++; stck[pt] = tt;
while (pt > 0)
{
temp = stck[pt];
if (bits[pt] == 0)
{
x = temp;
y = find(x,1);
if (pt >= 2 && stck[pt-1] == y)
{
resnum++; res[resnum] = stck[pt] ^ stck[pt-1];
del(stck[pt],0);
del(stck[pt-1],1);
pt -= 2;
} else
{
pt++; stck[pt] = y; bits[pt] = 1;
}
} else
{
y = temp;
x = find(y,0);
if (pt >= 2 && stck[pt-1] == x)
{
resnum++; res[resnum] = stck[pt] ^ stck[pt-1];
del(stck[pt-1],0);
del(stck[pt],1);
pt -= 2;
} else
{
pt++; stck[pt] = x; bits[pt] = 0;
}
}
}
}
sort(res+1,res+n+1);
fo(i,1,n-1) printf("%d ",res[i]); printf("%d\n",res[n]);
}
return 0;
}
1004 equation
考虑从左到右枚举答案的区间,那么左边的方程一定是取负,右边的方程一定是取正
一边扫描一边计算答案就好
还有就是输出答案为
0
0
0的时候要输出
0
/
1
0/1
0/1(分母忘记置0了,一字节惨案)
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MOD 998244353
#define N 100005
#define ll long long
using namespace std;
struct www{ll a,b;} f[N],g[N];
ll T,n,i,k,P,Q,C,flag;
ll res1[N],res2[N];
bool cmp(const www &x,const www &y) {return x.b*y.a > x.a*y.b;}
bool ck(ll P,ll Q,ll x,ll y)
{
if (Q * x <= P * y) return true; else return false;
}
bool check(ll P,ll Q,ll x,ll y)
{
if (P < 0) {P *= -1; Q *= -1;}
if (x != -1)
{
if (ck(g[x].a,-g[x].b,P,-Q) == false) return false;
}
if (y != n + 1)
{
if (ck(P,-Q,g[y].a,-g[y].b) == false) return false;
}
return true;
}
ll get_gcd(ll x,ll y)
{if (x % y == 0) return y; else return get_gcd(y,x%y);}
int main()
{
scanf("%lld",&T);
while (T--)
{
scanf("%lld%lld",&n,&C);
fo(i,1,n) scanf("%lld%lld",&f[i].a,&f[i].b);
sort(f+1,f+n+1,cmp);
k = 0; i = 1;
while (i <= n)
{
k++; g[k].a = f[i].a; g[k].b = f[i].b; i++;
while (i <= n)
{
if (g[k].b*f[i].a==g[k].a*f[i].b)
{
g[k].a += f[i].a; g[k].b += f[i].b;
i++;
} else break;
}
}
P = Q = 0; n = k; Q = -C;
fo(i,1,n) {P -= g[i].a; Q -= g[i].b;}
if (P == 0 && Q == 0) {cout<<-1<<endl; continue;}
k = 0;
if (P != 0)
if (check(P,Q,-1,1)) {k++; res1[k] = P; res2[k] = -Q;}
flag = 0;
fo(i,1,n)
{
P += 2 * g[i].a; Q += 2 * g[i].b;
if (P == 0 && Q == 0) {flag = 1; break;}
if (P != 0)
if (check(P,Q,i,i+1)) {k++; res1[k] = P; res2[k] = -Q;}
}
if (flag == 1) cout<<-1<<endl; else
if (k == 0) cout<<0<<endl; else
{
fo(i,1,k) f[i].a = res1[i],f[i].b = res2[i];
fo(i,1,k) if (f[i].a < 0) {f[i].a *= -1; f[i].b *= -1;}
fo(i,1,k) f[i].b *= -1;
sort(f+1,f+k+1,cmp);
fo(i,1,k) f[i].b *= -1;
n = k; k = 0; i = 1;
while (i <= n)
{
k++; g[k].a = f[i].a; g[k].b = f[i].b; i++;
while (i <= n)
{
if (g[k].b*f[i].a==g[k].a*f[i].b)
{
g[k].a += f[i].a; g[k].b += f[i].b;
i++;
} else break;
}
}
cout<<k;
if (k == 0) cout<<endl; else cout<<" ";
fo(i,1,k)
{
P = g[i].a; Q = g[i].b;
ll f = 1;
if (P < 0) {f *= -1; P *= -1;}
if (Q < 0) {f *= -1; Q *= -1;}
if (f == -1) cout<<"-";
ll gcd;
if (Q == 0) gcd = P; else gcd = get_gcd(P,Q);
cout<<Q/gcd<<"/"<<P/gcd;
if (i == k) cout<<endl; else cout<<" ";
}
}
}
return 0;
}
1005 permutation 1
当
n
≤
9
n \le 9
n≤9时暴力即可
当
n
>
9
n > 9
n>9时,考虑答案的形式
字典序最小的情况是
n
,
1
,
2
,
3.....
n,1,2,3.....
n,1,2,3.....,然后呢,找找规律就可以发现前面若干个答案一定是
n
,
1
,
2.....
n,1,2.....
n,1,2.....的形式,因为最前面那些数最小了
实际上当
n
>
9
n>9
n>9时,由于
8
!
>
10000
8!>10000
8!>10000,所以最多只会动最后8位,所以对后8位全排列即可
再然后,如果你打表找找规律就能发现,后8位的排列第k小就是字典序第k小(直接暴力排序也可以)
1006 string matching
题目给的程序统计的是从每个位置出发,和开头匹配的长度
也就是后缀和原串的公共前缀长度
直接把kmp算法的next数组统计一下就好
注意一下fail的时候的+1
#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int T,m,i,j,k,len,l;
long long res;
char a[1000005];
int nxt[1000005];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%s",a); m = strlen(a);
for (j = 0;1 + j < m && a[j] == a[1+j]; j++);
nxt[1] = j; k = 1;
for (i = 2;i < m; i++)
{
len = k + nxt[k]; l = nxt[i-k];
if (l < len - i)
nxt[i] = l;
else
{
for (j = max(0,len-i);i+j<m && a[j] == a[i+j]; j++);
nxt[i] = j; k = i;
}
}
res = 0;
//fo(j,1,m-1) cout<<nxt[j]<<" "; cout<<endl;
fo(j,1,m-1)
if (nxt[j] == m - j) res = res + nxt[j]; else res = res + nxt[j] + 1;
cout<<res<<endl;
}
return 0;
}
1007 permutation 2
把题目要求看作是从
x
x
x走到
y
y
y且经过所有点
首先一个显然的结论是,一条路总体上最多只能走两遍
什么意思呢,比如从1走到n,那么要么是1直接到n,要么是1跳着走到n,再跳着走回1
回到原题,不妨令
x
<
y
x<y
x<y,那么答案一定是
x
=
>
1
=
>
x
+
1
=
>
y
−
1
=
>
n
=
>
y
x=>1=>x+1=>y-1=>n=>y
x=>1=>x+1=>y−1=>n=>y
而显然
x
=
>
1
=
>
x
+
1
x=>1=>x+1
x=>1=>x+1的走法是唯一的,来回必须刚好间隔,另一边同理
所以不同的方案只会出现在
x
+
1
=
>
y
−
1
x+1=>y-1
x+1=>y−1
因为可以
12
12
12走,也可以
1324
1324
1324走,所以有
f
[
i
]
=
f
[
i
−
1
]
+
f
[
i
−
3
]
f[i]=f[i-1]+f[i-3]
f[i]=f[i−1]+f[i−3]
注意一下当
x
=
1
x=1
x=1时不需要加一,同理
y
=
n
y=n
y=n时不需要减一
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MOD 998244353
#define N 100005
using namespace std;
int NN,i,n,x,y,T;
int f[N];
int main()
{
scanf("%d",&T);
NN = 100000;
f[0] = 1; f[1] = 1; f[2] = 1;
fo(i,3,NN) f[i] = (f[i-1] + f[i-3]) % MOD;
fo(i,1,T)
{
scanf("%d%d%d",&n,&x,&y);
if (x > y) swap(x,y);
if (x != 1) x++;
if (y != n) y--;
if (x > y) cout<<0<<endl; else
cout<<f[y-x]<<endl;
}
return 0;
}
1008 line symmetric
答题思路都是枚举对称轴所在的位置,我的想法和std一样,要么
a
i
和
a
i
−
1
a_i和a_{i-1}
ai和ai−1作为对称点,要么
a
i
和
a
i
−
2
a_i和a_{i-2}
ai和ai−2作为对称点,队友的做法是,分别美剧枚举
a
1
a_1
a1、
a
2
a_2
a2、
a
、
3
a、3
a、3和所有点(要排除点在对称轴上和坏点的情况)
不过check的时候有一个最致命的非法情况没有考虑到:当前的两个点不但要关于对称轴对称,还得在同侧,也就是说在从对称轴左侧出发找的点必须始终在左侧,否则就会出现非简单多边形的情况
然后我们一直在判断答案非法的情况。。。。想了无数种特殊情况还是搞不出来
实际上所有的非法情况归结起来就是这样