PASSWORD系列赛题解
作者:互联网
T1.PASSWORD0--Graph
时间限制: 100ms
题目背景
此题为PASSWORD1,2的前奏。。。
题目描述
小Z十分的生气!他急于前往小L家,可在他眼前出现了由n个城市,m条道路组成的图,每条路通过都需要一定时间和一定费用,小Z想走最小路径到小L家。但眼前的景象却难倒了兢兢业业学习计算机的他,他需要你的求助。
最小路径的定义如下:定义每条小Z所在城市到小L家所在城市的路径总时间是各条道路旅行时间的和,总费用是各条道路所支付费用的总和。一条路径越快,或者费用越低,该路径就越好。严格地说,如果一条路径比别的路径更快,而且不需要支付更多费用,它就比较好。反过来也如此理解。如果没有一条路径比某路径更好,则该路径被称为最小路径。
最小路径可能不止一条,也有可能不存在路径
现在,小Z想知道他有多少种方法到小L家。
输入格式:
第一行有四个整数,城市总数 n,道路总数 m,起点和终点城市 s,e;
接下来的 m 行每行描述了一条道路的信息,包括四个整数,两个端点 p,r,费用 c,以及时间 t;
两个城市之间可能有多条路径连接。
输出格式:
仅一个数,表示小Z的走法总数。
输入样例:
4 5 1 4
2 1 2 1
3 4 3 1
2 3 1 2
3 1 1 4
2 4 2 4
输出样例:
2
说明
对于全部数据,1≤n≤100,0≤m≤300,1≤s,e,p,r≤n,0≤c,t≤100,保证 s != e,p != r。
标签:
最短路,动态规划,树状数组,线段树
若边只有一个权值,很容易想到用最短路求解
但此题中,边有两个权值,于是需要额外添加一维状态(SPFA中dis数组)
结合题目中问路径条数,可想到dp
f(i,j)表示在i点且费用为j时的最少时间
(k,i)为k -> i有向边,Cost为费用,Tim为时间
P(i)为存在(k,i)边的点集
$$
f[i][j] = Min{f[k][j - cost(k,i)] + Tim(k,i)}(k \in P(i))
$$
期望时间复杂度:
$$
O(K * N^2 * V)(K为迭代常数)
$$
但这样,还是无法通过此题(100ms),所以要尝试对这个算法进行优化
在求解过程中,考虑新状态f(i,j),若已存在f(i,k)满足k<j且f(i,k) < f(i,j)
显然,f(i,j)不是最优解
于是可以进行剪枝:
对于每一个新状态f(i,j),我们查询f(i,0~j)的最小值MinT,仅当MinT > f(i,j)时才更新,在实现时,可用树状数组维护每一个f(i),这样增加了查询速度
期望时间复杂度:
$$
O(K * N^2 * V * Log_2N)
$$
它的实际效率比未优化前快得多
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e4 + 5;
struct E
{
int next,to,t,w;
}e[605];
int id,f[605],n,m,s,t,ans,que[1000005][2],dis[105][N],vis[105][N],tree[105][N];
void add(int x,int y,int z,int w)
{
id++;
e[id].to = y;
e[id].next = f[x];
e[id].t = z;
e[id].w = w;
f[x] = id;
}
inline int lowbit(int x)
{
return x & (-x);
}
int GET_MIN(int x,int y)//查询dis[x][0....y]的最小值
{
++y;
int Min = 1e7;
while(y > 0)
{
Min = min(tree[x][y],Min);
y -= lowbit(y);
}
return Min;
}
void update(int x,int y,int val)
{
++y;
while(y <= 100 * n)
{
tree[x][y] = min(tree[x][y],val);
y += lowbit(y);
}
return ;
}
void spfa()
{
memset(dis,63,sizeof dis);
memset(tree,63,sizeof tree);
que[1][0] = s;
que[1][1] = 0;
dis[s][0] = 0;
update(s,0,0);
for(int q1 = 1,q2 = 1;q1 <= q2; ++q1)
{
int x = que[q1][0],f1 = que[q1][1];
vis[x][f1] = 0;
for(int i = f[x];i;i = e[i].next)
{
int f2 = f1 + e[i].w,y = e[i].to;
if(GET_MIN(y,f2) > dis[x][f1] + e[i].t)
{
dis[y][f2] = dis[x][f1] + e[i].t;
update(y,f2,dis[y][f2]);
if(!vis[y][f2])
{
vis[y][f2] = 1;
que[++q2][0] = y;
que[q2][1] = f2;
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m >> s >> t;
for(int i = 1;i <= m; ++i)
{
int x,y,w,z;
cin >> x >> y >> w >> z;
add(x,y,z,w);
add(y,x,z,w);
}
spfa();
int Min = dis[0][0];
for(int i = 0;i <= n * 100; ++i)
if(dis[t][i] < Min)
{
ans++;
Min = dis[t][i];
}
cout << ans << endl;
return 0;
}
T2.PASSWORD1--Maths
时间限制:
- 1~7评测点,100ms
- 8~10评测点,200ms
题目描述
蒟蒻小Z同学历经千辛万苦(见PASSWORD0),来到了小L同学家门口,却发现此门竟是一扇密码门,门上显示一个整数n,要求你将1~n的数排列,使得所有数都不在原位上,密码为这样排列的方案数,由于小L为小Z考虑,因此数太大,于是请将答案取模10007后再输入密码。小Z由于智商不够,他又找到了你,他急需你的帮助!
输入格式:
输入整数n
输出格式:
输出小L家密码门的密码
输入样例:
4
输出样例:
9
说明
对于10%的数据,1 <= n <= 100
对于100%的数据,1 <= n <= 1e7
标签:
排列组合,错位排序
此题可直接运用错位排序递推式
D(i)表示将1~n的数重新排列,使所有数都不在原位上,排列的方案数
$$
D(1) = 0\
D(2) = 1\
D(n) = (n - 1) * (D(n - 1) + D(n - 2))
$$
证明:
显然对于n=1、2时,有D1=0,D2=1。
当n≥3时,在n个不同元素中任取一个元素ai不排在与其编号相对应的i位,必排在剩下n-1个位置之一,所以ai有n-1种排法。
对ai每一种排法,如ai排在j位,对应j位的元素aj的排位共有两种情况:
第一种情况:aj恰好排在i位上,此时,ai排在j位,aj排在i位,元素ai,aj排位已定,还剩n-2个元素,它们的排位问题就转化为n-2个元素全错位排列数,应有Dn-2种;
第二种情况:aj不排在i位上,此时,ai仍排在j位,aj不排在i位,即此时aj有一个不能排的位置,也就是说,除了ai外,还有n-1个元素,每个元素均有一个不能排的位置,问题就可转化为n-1个元素得全错位排列,排列数为Dn-1,由乘法原理和加法原理可得:Dn=(n-1)(Dn-1+Dn-2)(n≥3)。
代码:
#include <iostream>
using namespace std;
const int MOD = 10007;
int n,D[10000005];
int main()
{
cin >> n;
D[1] = 0;
D[2] = 1;
for(int i = 3;i <= n; ++i)
{
D[i] = (i - 1) % MOD * (D[i - 1] + D[i - 2]);
D[i] %= MOD;
}
cout << D[n] << endl;
return 0;
}
T3.PASSWORD2--Find
时间限制: 200ms
题目描述
由于小Z频繁打开小L家的门,于是小L把旧密码门换成了全新的密码门。 门上显示n个整数数对(ai,bi),还有一整数m,让你选出m个数对,密码为选出的m对(∑ai)/(∑bi)的最大值。
小Z突然发现,密码要保留四位小数,他十分想进去,需要求助你,让你来帮他打开全新密码门。
输入格式:
输入两整数n,m,第2行到n+1行为数对(ai,bi)
输出格式:
输出小L家密码门的密码
输入样例:
3 2
1 2
2 3
3 4
输出样例:
0.7142
说明
对于10%的数据,1 <= m <= n <= 20
对于100%的数据,1 <= m <= n <= 5000,1 <= |ai|,bi <= 100
数据均为Python随机生成
答案保留四位小数!!!
标签:
二分答案,01分数规划
此题几乎为模板题
$$
(\Sigma a_i)/(\Sigma b_i) \geq k\
\Sigma a_i \geq k * \Sigma b_i\
\Sigma a_i - k * \Sigma b_i \geq 0
$$
以上为二分的判断条件
c(i) = a(i) - k * b(i)
对c(i)从大到小排序
c[1] + ... + c[m] = sum
sum >= 0即为可行,反之不可行
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,a[19002],b[19002];
long double c[19002];
bool cmp(long double a,long double b)
{
return a > b;
}
bool check(double g)
{
for(int i = 1;i <= n;i++)
c[i] = a[i] - g * b[i];
sort(c + 1,c + n + 1,cmp);
long double q = 0;
for(int i = 1;i <= m;i++)
q += c[i];
return q >= 0;
}
int main()
{
cin >> n >> m;
for(int i = 1;i <= n;i++)
cin >> a[i] >> b[i];
//[l,r)
double l = -10001,r = 10001,mid;
while(l + 0.0001 < r)
{
mid = (l + r) / 2;
if(check(mid))
l = mid;
else
r = mid;
}
printf("%.4lf",l);
return 0;
}
T3.PASSWORD3--String
时间限制: 300ms
题目背景
hzy巨佬AK完NOIP后,于是想学(tui)习(fei),无聊中的他构思出一款游戏,把游戏规则告诉了小L、小Z和你,但需要计算机来快速地判断输赢,由于hzy巨佬不屑于开发这种如此简单的游戏,于是想让你开发出判断输赢的程序
题目描述
小Z终于走进了小L家的家门,由于十分疲惫,想跟小L玩hzy开发的游戏,游戏规则如下:
小L和小Z分别给出字符串A、B(只含大,小写字母)和整数C、D,由于他俩十分有默契,A包含B(保证A比B长)。
这时统计出B在A中出现的位置(位置从1开始计算,总是记B在A中每次开始的位置),组成了一个集合{pos}。
定义"分契合值"li=min(abs((int)A[posi]-(int)A[posj]))(1 <= j < i),特殊的,l1=A[pos1],"总契合值"S为∑li
若C离S较近,输出"L";
若D离S较近,输出"Z";
若C离S与D离S一样近,输出"AK NOIP!"(有叹号)
小Z十分贪玩,于是决定玩T局,现在请你写一个程序,让你给出每局的输赢情况
输入格式:
第一行一个整数T
之后4*T行,每四行中依次有:字符串A 字符串B 猜测数C 猜测数D
输出格式:
共T行,第i行表示第i局的输赢情况
输入样例:
3
AB
AB
1
1
AB
AB
1
5
AB
AB
5
1
输出样例:
AK NOIP!
L
Z
说明
B在A中出现次数<=100000,B.size()<=A.size()<=1e6,T<=20,C,D均在int范围内
标签:
平衡树,KMP
模板题,略
平衡树部分可见[HNOI2002]营业额统计
代码:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;
struct node
{
int lc,rc,cnt,pos,vis;
#define lc(x)t[x].lc
#define rc(x)t[x].rc
#define p(x)t[x].pos
#define c(x)t[x].cnt
#define v(x)t[x].vis
}t[1000005];
char A[10000005],B[1000005];
int T,C,D,P[1000005],lenA,lenB,id,Ans,rt,pool;
void init()
{
memset(P,0,sizeof P);
id = 0;
}
void Zig(int &k)
{
int y = lc(k);
lc(k) = rc(y);
rc(y) = k;
k = y;
}
void Zag(int &k)
{
int y = rc(k);
rc(k) = lc(y);
lc(y) = k;
k = y;
}
void Insert(int &k,const int &key)
{
if(!k)
{
k = ++pool;
v(k) = key;
p(k) = rand();
c(k) = 1;
lc(k) = rc(k) = 0;
return ;
}
if(v(k) == key)
++c(k);
else if(key < v(k))
{
Insert(lc(k),key);
if(p(lc(k)) < p(k))
Zig(k);
}
else
{
Insert(rc(k),key);
if(p(rc(k)) < p(k))
Zag(k);
}
return ;
}
int QueryPre(const int &key)
{
int x = rt,res = -99999999;
while(x)
{
if(v(x) <= key)
res = v(x),x = rc(x);
else
x = lc(x);
}
return res;
}
int QuerySuf(const int &key)
{
int x = rt,res = 99999999;
while(x)
{
if(v(x) >= key)
res = v(x),x = lc(x);
else
x = rc(x);
}
return res;
}
void pre()
{
P[1] = 0;
int j = 0;
for(int i = 1;i < lenB; ++i)
{
while(j > 0 && B[j + 1] != B[i + 1])
j = P[j];
if(B[j + 1] == B[i + 1])
j++;
P[i + 1] = j;
}
}
void KMP()
{
int j = 0,x,y;
for(int i = 0;i < lenA; ++i)
{
while(j > 0 && A[i + 1] != B[j + 1])
j = P[j];
if(A[i + 1] == B[j + 1])
j++;
if(j == lenB)
{
if(id == 0)
Insert(rt,Ans = (int)A[i - j + 2]);
else
{
x = QueryPre((int)A[i - j + 2]),y = QuerySuf((int)A[i - j + 2]);
Ans += min((int)A[i - j + 2] - x,y - (int)A[i - j + 2]);
Insert(rt,(int)A[i - j + 2]);
}
j = P[j];
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> T;
while(T--)
{
init();
cin >> A + 1 >> B + 1 >> C >> D;
lenA = strlen(A + 1);
lenB = strlen(B + 1);
pre();
KMP();
if(abs(C - Ans) < abs(D - Ans))
cout << "L" << endl;
else if(abs(C - Ans) > abs(D - Ans))
cout << "Z" << endl;
else
cout << "AK NOIP!" << endl;
}
return 0;
}
T5.PASSWORD4--Maintain
题目背景
ZHT由于HZY的游戏而十分愤怒,于是他前往HZY家修改游戏源代码...
题目描述
ZHT来到HZY的电脑前,只见屏幕上显示:“回答如下问题,进入系统。”
这时,ZHT发现的HZY桌上的纸条:
问题如下:
有初始数列a1,a2,...,an,还有模数MOD
操作:
(1)把数列中的一段数全部乘一个值;
(2)把数列中的一段数全部加一个值;
(3)把数列中的一段数全部取模一个值;
问题:
数列中的一段数的和,由于答案可能很大,你只需回答这个数模MOD的值。
擅长数学的你(ZHT),离开计算机独立试试吧!
----------------HZY
蒟蒻ZHT无能为力,只好求助拥有计算机的你
输入格式
第一行两个整数n和MOD(1≤MOD≤1e9)。n,m <= 1e5
第二行含有N个非负整数,从左到右依次为a1,a2,…,an, (0≤ai≤1e9,1≤i≤n)。
第三行有一个整数m,表示操作总数。
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1:“1 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai×c(1≤t≤g≤N,1≤c≤1e9)。
操作2:“2 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai+c (1≤t≤g≤N,1≤c≤1e9)。
操作3:“3 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai%c (1≤t≤g≤n,1≤c≤1e9)。
问题:“Q t g”(不含双引号)。询问所有满足t≤i≤g的ai的和模MOD的值 (1≤t≤g≤n)。
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
输出格式
对每个问题,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
样例输入
7 43
1 2 3 4 5 6 7
5
1 2 5 5
Q 2 4
2 3 7 9
Q 1 3
Q 4 7
样例输出
2
35
8
数据范围:
对于30%的数据,n,m ≤ 1e3
对于100%的数据,n,m ≤ 1e5
思路
线段树半模板题
区间加法,区间乘法都很好做,只需维护懒标记add[],mul[]即可
(注:mul[]初始值为1)
区间取模:显然,区间取模无法用懒标记解决,但大量的单点修改速度会很慢,但注意到,只有被取模数大于等于模数,取模才有效,故想到额外维护区间最大值,若最大值小于模数,当前区间就不用考虑,这样就加快了许多
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;
ll n,MOD,m,add[N << 2],mul[N << 2],sum[N << 2],Max[N << 2],a[N];
inline void Mul(int k,ll v)
{
mul[k] *= v;
add[k] *= v;
sum[k] *= v;
Max[k] *= v;
Max[k] %= MOD;
mul[k] %= MOD;
add[k] %= MOD;
sum[k] %= MOD;
return ;
}
inline void Add(int k,int l,int r,ll v)
{
add[k] += v;
sum[k] += (r - l + 1) * v;
Max[k] += v;
Max[k] %= v;
sum[k] %= MOD;
add[k] %= MOD;
return ;
}
inline void PushUP(int k)
{
sum[k] = (sum[k << 1] + sum[k << 1 | 1]) % MOD;
Max[k] = (max(Max[k << 1],Max[k << 1 | 1])) % MOD;
return ;
}
inline void build(int k,int l,int r)
{
if(l == r)
{
sum[k] = Max[k] = a[l];
return ;
}
int mid = l + r >> 1;
build(k << 1,l,mid);
build(k << 1 | 1,mid + 1,r);
PushUP(k);
}
inline void PushdownMul(int k,int l,int r)
{
if(mul[k] == 1)
return ;
Mul(k << 1,mul[k]);
Mul(k << 1 | 1,mul[k]);
mul[k] = 1;
}
inline void PushdownAdd(int k,int l,int r)
{
if(!add[k])
return ;
int mid = l + r >> 1;
Add(k << 1,l,mid,add[k]);
Add(k << 1 | 1,mid + 1,r,add[k]);
add[k] = 0;
}
inline void modify(int k,int l,int r,int L,int R,ll v,bool ch)
{
if(L <= l && r <= R)
{
if(ch == 0)
Mul(k,v);
else
Add(k,l,r,v);
return ;
}
int mid = l + r >> 1;
PushdownMul(k,l,r);
PushdownAdd(k,l,r);
if(L <= mid)
modify(k << 1,l,mid,L,R,v,ch);
if(mid < R)
modify(k << 1 | 1,mid + 1,r,L,R,v,ch);
PushUP(k);
}
inline void modifyMOD(int k,int l,int r,int L,int R,ll v)
{
if(Max[k] < v)
return ;
if(l == r)
{
sum[k] %= v;
Max[k] %= v;
add[k] %= v;
mul[k] %= v;
return ;
}
int mid = l + r >> 1;
PushdownMul(k,l,r);
PushdownAdd(k,l,r);
if(L <= mid)
modifyMOD(k << 1,l,mid,L,R,v);
if(mid < R)
modifyMOD(k << 1 | 1,mid + 1,r,L,R,v);
PushUP(k);
}
inline ll query(int k,int l,int r,int L,int R)
{
if(L <= l && r <= R)
return sum[k];
int mid = l + r >> 1;
PushdownMul(k,l,r);
PushdownAdd(k,l,r);
ll res = 0;
if(L <= mid)
{
res += query(k << 1,l,mid,L,R);
res %= MOD;
}
if(mid < R)
{
res += query(k << 1 | 1,mid + 1,r,L,R);
res %= MOD;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> MOD;
for(int i = 1;i <= n << 2; ++i)
mul[i] = 1;
for(int i = 1;i <= n; ++i)
cin >> a[i];
build(1,1,n);
cin >> m;
ll c;
int t,g;
for(int i = 1;i <= m; ++i)
{
char op;
cin >> op >> t >> g;
if(op == '1')
{
cin >> c;
modify(1,1,n,t,g,c,0);
}
else if(op == '2')
{
cin >> c;
modify(1,1,n,t,g,c,1);
}
else if(op == '3')
{
cin >> c;
modifyMOD(1,1,n,t,g,c);
}
else
cout << query(1,1,n,t,g) << endl;
}
}
标签:include,lc,int,题解,系列赛,++,ai,rc,PASSWORD 来源: https://www.cnblogs.com/pipa-peng/p/10386046.html