94EB5349286B5A86
由BCD出题组出题(因为最强的 A A A(勋总)去参赛了,所以由 A B C ABC ABC出题组变名为 B C D BCD BCD出题组)
组长:贝
组员:杰、弛、举、廖、曾
题目陈述
输入格式
输出格式
输入样例
输出样例
(可选)样例解释
数据规模及约定
这次设计的数据,因为蓝桥比赛是 O I OI OI赛制,我们的初衷是尽量保证黑箱测试
因为PTA
上的赛制无法设置OI
赛制,故我们只能设置平时的IOI
赛制
除了签到题,可以通过 I O I IOI IOI赛制二分找出测试点的具体值以外,或者直接YES
或NO
就有5
分,我们是有意为之,并不打算设置多组数据来卡大家签到(但是在真正的比赛中,这类YES
orNO
的题目,必然是多组数据的,防止有人直接输出YES
可以得到一半分数)
而对于其他题目,比如勋总的求偶日记
,若不设置多组数据,则可以二分数据点,然后本地暴力跑,得出答案,再打表提交上去AC
,这在OI
赛制中是不可能出现的,而且多组数据也减小了随机数无技术含量,以及错误算法能骗分的可能性。
(这也是往年我校出题组出的卷子数据存在的问题,有人校赛可以靠这样拿很高分数,正式赛就翻车的原因)
有以下题目非蓝桥题风,原因如下:
签到题1、签到题2,非蓝桥题风,为了考虑想拿创新学分的同学,同时一定意义综合了按位枚举的蓝桥杯暴力法
中档题1:非蓝桥题分,并查集模板,集体送分,为了榜单不那么难看,大家的做题体验不那么差hhh
中档题2:非蓝桥题风,枚举子集,是蓝桥省赛中常用的骗分手段
中档3-压轴2:蓝桥题风,整体后半部分卷子结构“动态规划+数据结构+字符串”,然后数论嵌套在各个题目当中。
整场卷子我们演了三次卷子,所以正常比赛也算顺利(中途临时加时限,就是为了送分hhh)
U1S1说题目质量确实很高,给很多ACM银牌选手演过了题目,质量还算高,但是还是有一定的难度
因为现应对蓝桥省赛时,难度也大致同这次校选
出题人:贝
str(17, '0') + str(13, '1');
str(1, '0') + str(13, '1')+ str(4,'0')+str(12,'1');
,卡掉仅从低位连续不到13个1就跳出的错误算法。str(2, '0') + str(5, '1') + str(3, '0') + str(20, '1');
,测试连续情况大于13个1的情况。str(30, '1')
,极端情况,全1str(12, '1') + str(1, '0') + str(12, '1') + str(1, '0') + str(4, '1');
,测试NO
基本情况,且1的总数大于13但是不连续str(30, '0');
,极端情况全0str(6, '0') + str(9, '1') + str(6, '0') + str(9, '1')
01
循环节15次10
循环节15次1
则cnt++
,遇到0
则cnt
清零即可cnt==13
直接输出YES
NO
#include
int main()
{
int n;
scanf("%d", &n);
int cnt = 0;
for (int i = 0; i < 30; i ++ )
{
if((n & (1 << i)) == 0)
cnt = 0;
else {
cnt ++ ;
if(cnt == 13)
{
printf("YES\n");
return 0;
}
}
}
printf("NO\n");
}
&
的结果,依旧为 s t st st,则表示存在连续的 13 13 13个 1 1 1NO
#include
int main()
{
int n, st = (1 << 13) - 1; //2的13次方 - 1
//二进制连续13个1
scanf("%d", &n);
for (int i = 0; i + 13 - 1 < 30; i ++ )
{
if(((n >> i) & st) == st) //注意运算符优先级
{
printf("YES\n");
return 0;
}
}
printf("NO\n");
}
出题人:廖
给定两个整数x和y,有两个操作,操作1可以花费a元将x或y增加或减少1,操作2可以花费b元将x和y同时增加或减少1,
计算使x=y=0所需花费的最低金额。
测试点详情
第一个测试点:x
第二个测试点:x>y,a>b,结果在int范围内。
第三个测试点:x>y,a=b,结果在longlong范围内。
第四个测试点:x
#include
long long x, y, a, b;
int main(){
scanf("%lld%lld%lld%lld", &x, &y, &a, &b);
if(x > y) //x中保存小的数
{
long long temp = x;
x = y, y =temp;
}
if(2 * a > b) printf("%lld\n", b * x + (y - x) * a);
else printf("%lld\n", a * (x + y));
}
#include
using namespace std;
int main()
{
long long x,y,a,b;
cin>>x>>y>>a>>b;
if(x>y) swap(x,y);
cout<<min(x*b+(y-x)*a,(x+y)*a);
return 0;
}
出题人:举
n个同学会形成若干个朋友圈,相当于是若干个集合。把消息通知给某个人需要花费一定代价,但是同一个朋友圈内消息传递不需要代价的,要求把消息通知给所有人的最小花费。
并查集模板题,只要求出每个同学所属的集合,然后遍历集合中每个人,选则消息通知代价的最小的那个人即可。
#include
using namespace std;
const int maxn = 3e4 + 10;
int fa[maxn];
void init() {
for (int i = 0; i < maxn; i++) {
fa[i] = i;
}
}
int find(int cur) {
return fa[cur]= fa[cur] == cur ? cur : find(fa[cur]);
}
void solve() {
init();
int n, m;
cin >> n >> m;
vector<int> c(n + 1);
int i, j;
for (i = 1; i <= n; i++)cin >> c[i];
for (i = 0; i < m; i++) {
int k;
cin >> k;
int first;
cin >> first;
int num;
for (j = 1; j < k; j++) {
cin >> num;
int r1 = find(first);
int r2 = find(num);
fa[r1] = r2;
}
}
unordered_map<int, int> mmid;
int ans = 0;
for (i = 1; i <= n; i++) {
int r = find(i);
if (mmid.count(r) != 0) {
mmid[r] = min(mmid[r], c[i]);
}
else {
mmid[r] = c[i];
}
}
for (auto val : mmid) {
ans += val.second;
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
solve();
return 0;
}
出题人:廖
给定一个数组,列举出该数组的所有子集(不包含空集),子集按字典序从小到大排序。
前言:这道题题意比较简单,思路也很简单,用二进制枚举或dfs都可以求解,但排序可能会卡到一部分人,如果将数字转化成字符串
处理相对比较麻烦,可以开一个结构体,结构体内放置一个vector,再用结构体排序即可。
测试点详情
第一个测试点:n=1,仅有一个元素。
第二个测试点:n=2,两个元素。
第三个测试点:n=3,三个元素。
第四个测试点:n=4,四个元素。
第五个测试点:n=5,五个元素。
第六个测试点:n=6,六个元素,且元素均小于10。
第七个测试点:n=7,七个元素,且元素均小于10。
第八个测试点:n=8,八个元素,且元素均小于10。
第九个测试点:n=9,九个元素,且元素均小于100。
vector
表示,接着放入set
中默认排序vector
存入到结构体中,排序即可,set
套vector
#include
using namespace std;
int n;
int totBit;
//{1,2,--,n}
int a[100];
int main()
{
cin >> n;
for (int i = 0; i < n ; i ++ )
cin >> a[i];
totBit = (1 << n) - 1;
set<vector<int> > s;
for (int i = 1; i <= totBit; i ++ )
{
// 当前状态为i
vector<int> v;
for (int j = 0; j < n; j ++ )
//总共有n位,为0 ~ (n - 1)
// 0~ n -1 ---> 1 ~ n
{
// if (i & (1 << j))
if (((i >> j) & 1) == 1)
{
v.push_back(a[j]);
}
}
s.insert(v);
}
// O(n log n)
for (vector<int> v : s)
{
for (int i = 0; i < v.size(); i ++ )
{
if ( i > 0) cout << ' ';
cout << v[i];
}
cout << '\n';
}
}
#include
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;i++)
typedef struct{
vector<int> v;
}node;
node a[2005];
int b[15];
bool cmp(node x,node y){
return x.v<y.v;
}
int main()
{
int n,cnt=0;
cin>>n;
rep(i,1,n) cin>>b[i];
for(int i=1;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if((1<<(j-1))&i) a[cnt].v.pb(b[j]);
}
cnt++;
}
sort(a,a+cnt,cmp);
for(int i=0;i<cnt;i++){
for(int j=0;j<a[i].v.size()-1;j++) cout<<a[i].v[j]<<" ";
cout<<a[i].v[a[i].v.size()-1];
cout<<endl;
}
return 0;
}
出题人:曾
a 0 = 1 , a n = 4 × a n − 1 + 2 a_0=1,a_n=4 \times a_{n-1}+2 a0=1,an=4×an−1+2 , 求 a n a_n an
算法一涉及了高中数学里“构造等比数列”,有做数学题那味了,然后出了这题。
共十个测试点:
构造等比数列,使用快速幂
a 0 = 1 , a n = 4 × a n − 1 + 2 a_0=1,a_n=4 \times a_{n-1}+2 a0=1,an=4×an−1+2
→ a n + 2 3 = 4 ( a n − 1 + 2 3 ) \rightarrow a_n+\cfrac{2}{3}=4(a_{n-1}+\cfrac{2}{3}) →an+32=4(an−1+32)
→ a n + 2 3 = 5 3 × 4 n \rightarrow a_n+\cfrac{2}{3}=\cfrac{5}{3} \times 4^n →an+32=35×4n
→ a n = 5 3 × 4 n − 2 3 = 5 × 4 n − 2 3 \rightarrow a_n=\cfrac{5}{3} \times 4^n-\cfrac{2}{3}=\cfrac{5 \times 4^n-2}{3} →an=35×4n−32=35×4n−2
快速幂 q p o w ( x , n , k ) : x n % k qpow(x,n,k): x^n\%k qpow(x,n,k):xn%k
#define ll long long
const ll mod= 1000000007LL;
ll qpow(ll x,ll n,ll k){
ll ans=1;
while(n){
if(n%2==1)ans=ans*x%k;
x=x*x%k;
n/=2;
}
return ans;
}
有分母,需要处理分母:
注意: a b % m o d ≠ a % m o d b \cfrac{a}{b}\%mod \neq {\cfrac{a\%mod}{b}} ba%mod=ba%mod
解释: a n = 5 × 4 n − 2 3 a_n=\cfrac{5 \times 4^n-2}{3} an=35×4n−2是推出来的公式,由于 a n a_n an必定是整数,所以 5 × 4 n − 2 3 \cfrac{5 \times 4^n-2}{3} 35×4n−2也必是整数,所以 5 × 4 n − 2 5 \times 4^n-2 5×4n−2必是3的倍数。取模过程中,如果分子直接对mod取模,可能由于取模造成分子不是3的倍数而造成 W r o n g A n s w e r Wrong Answer WrongAnswer。所以需要处理。
几个常识性的公式:
( a + b ) % c = ( ( a % c ) + ( b % c ) ) % c (a+b)\%c=((a\%c)+(b\%c))\%c (a+b)%c=((a%c)+(b%c))%c 防止溢出
( a − b ) % c = ( ( a % c ) − b + c ) % c (a-b)\%c=((a\%c)-b+c)\%c (a−b)%c=((a%c)−b+c)%c 加c, 防止出现负数
( a × b ) % c = ( ( a % c ) × ( b % c ) ) % c (a \times b) \%c =((a\%c) \times (b\%c)) \%c (a×b)%c=((a%c)×(b%c))%c 防止溢出
逆元公式处理分母:
a b % c = ( a × b c − 2 ) % c \frac{a}{b}\%c=(a \times b^{c-2})\%c ba%c=(a×bc−2)%c
ll niyuan(ll a,ll b,ll c){
return a*qpow(b,mod-2,mod)%mod;
}
ll solve1(ll x)
{
return niyuan(5 * qpow(4, x, mod) - 2 + mod, 3, mod) % mod;
}
a 3 % m o d = a % ( m o d × 3 ) m o d \cfrac{a}{3}\%mod ={\cfrac{a\%(mod \times 3)}{mod}} 3a%mod=moda%(mod×3)
解释: 5 × 4 n − 2 5 \times 4^n-2 5×4n−2必是 3 3 3的倍数, 为了最后分子能被 3 3 3整除,那么在快速幂取余的过程中,分子对 3 × m o d 3 \times mod 3×mod取余,这样可以保证快速幂算出来的结果可以被 3 3 3整除
ll solve2(ll x)
{
return (5 * qpow(4, x, 3 * mod) - 2 + 3 * mod) / 3 % mod;
}
理论基础: 矩阵相乘满足结合律
不构造等比数列,构造矩阵,用矩阵快速幂来做:
因为:
[ 4 1 0 1 ] × [ a n − 1 2 ] = [ 4 × a n − 1 + 2 2 ] = [ a n 2 ] \left[ \begin{matrix} 4 &1 \\0 &1\end{matrix} \right] \times \left[ \begin{matrix} a_{n-1} \\2 \end{matrix} \right]=\left[ \begin{matrix} 4 \times a_{n-1}+2 \\2\end{matrix} \right]=\left[ \begin{matrix} a_n \\2 \end{matrix} \right] [4011]×[an−12]=[4×an−1+22]=[an2] 且 a 0 = 1 a_0=1 a0=1
所以:
[ a n 2 ] = [ 4 1 0 1 ] n × [ 1 2 ] \left[ \begin{matrix} a_n \\2 \end{matrix} \right] = {\left[ \begin{matrix} 4 &1 \\0 &1\end{matrix} \right]}^{n} \times \left[ \begin{matrix} 1 \\2 \end{matrix} \right] [an2]=[4011]n×[12]
struct matrix
{
ll num[2][2];
int n, m;
struct matrix operator*(struct matrix b)
{
struct matrix c;
c.n = n;
c.m = b.m;
for (int i = 0; i < c.n; i++)
{
for (int j = 0; j < c.m; j++)
{
c.num[i][j] = 0;
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < b.m; j++)
{
for (int k = 0; k < m; k++)
{
c.num[i][j] += num[i][k] * b.num[k][j];
c.num[i][j] %= mod;
}
}
}
return c;
}
};
ll solve(ll x)
{
struct matrix a, b;
a.m = a.n = 2;
a.num[0][0] = 4;
a.num[0][1] = 1;
a.num[1][0] = 0;
a.num[1][1] = 1;
b.n = 2, b.m = 1;
b.num[0][0] = 1;
b.num[1][0] = 2;
while (x)
{
if (x & 1)
{
b = a * b;
}
x >>= 1;
a = a * a;
}
return b.num[0][0];
}
出题人:贝
newSum[k]=a[k]
,newSum[k+1]=a[k]+a[k+1]
,以此类推newSum[k]>=0
#include
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int T, n, m;
int a[N];
LL sum[N], sufMin[N], preMin[N];
int main()
{
cin.tie(0);
cout.tie(0);
cin >> n;
preMin[0] = sufMin[n + 1] = INT_MAX;
for (int i = 1; i <= n; i ++ ) //下标改为1开始
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
preMin[i] = min(preMin[i - 1], sum[i]);
}
for (int i = n; i >= 1; i -- )
sufMin[i] = min(sufMin[i + 1], sum[i]);
int ans = 0;
for (int i = 1; i <= n; i ++ )
{
if(sufMin[i] - sum[i - 1] >= 0 && preMin[i - 1] + sum[n] - sum[i - 1] >= 0)
// 原来下标[i,n]这一段都合法
// 原来下标[1,i - 1]这一段都合法
ans ++ ;
}
cout << ans << '\n';
}
出题人:曾
有序列 A , B A,B A,B,按照 A A A中元素出现的顺序,构造 B B B的最长非递减子序列
标题已经提示了:LIS => Longest Increasing Subsequence =>最长递增子序列(实则是最长非递减子序列),本题是PAT真题(PAT甲级真题1045)改编,原题数据友好得多,本题还需要哈希与二分。
共十个测试点:
涉及算法:思维,最长非递减子序列,二分,哈希(或者map)。
#include
using namespace std;
int A[4000 + 10], B[800000 + 10];
int main(int argc, char *argv[])
{
int n, m, i, j;
cin >> n >> m;
for (j = 1; j <= n; j++)
scanf("%d", &A[j]);
for (i = 1; i <= m; i++)
{
scanf("%d", &B[i]);
for (j = 1; j <= m; j++)
{
if (A[j] == B[i])
break;
}
B[i] = j;
}
vector<int> ans;
for (i = 1; i <= m; i++)
{
if (ans.size() == 0 || ans[ans.size() - 1] <= B[i])
ans.push_back(B[i]);
else
{
for (j = 0; j < ans.size(); j++)
{
if (ans[j] > B[i])
{
ans[j] = B[i];
break;
}
}
}
}
cout << ans.size() << endl;
return 0;
}
#include
using namespace std;
int A[4000 + 10], B[800000 + 10];
map<int, int> pot;
int main(int argc, char *argv[])
{
int n, m, i, j;
cin >> n >> m;
for (j = 1; j <= n; j++)
{
scanf("%d", &A[j]);
pot[A[j]] = j;
}
for (i = 1; i <= m; i++)
{
scanf("%d", &B[i]);
B[i] = pot[B[i]];
}
vector<int> ans;
for (i = 1; i <= m; i++)
{
if (ans.size() == 0 || ans[ans.size() - 1] <= B[i])
ans.push_back(B[i]);
else
ans[upper_bound(ans.begin(), ans.end(), B[i]) - ans.begin()] = B[i];
}
cout << ans.size() << endl;
return 0;
}
出题人:贝
mod
为质数,则有 a b % m o d = a b % ( m o d − 1 ) % m o d a^b\%mod=a^{b\%(mod-1)}\%mod ab%mod=ab%(mod−1)%modmod-1
,因为mod
是质数,其欧拉函数的值为mod-1
,故指数取模的值为mod-1
)dp
数组c[]
累加和爆LL
的情况AC
说明没有锅,因为数据是通过标程生成的,如果WA
来才说明数据有问题。最后使用欧拉降幂的结果是AC
,说明不存在中途指数爆LL
的情况)LL
的情况,则需要使用欧拉降幂mod-1
#include
using namespace std;
typedef long long LL;
const LL md = 1e9 + 7;
int T;
LL c[65][65], n, ans = 1; //组合数
vector<int> nums; //二进制分解
LL fpow(LL a, LL b)
{
a %= md;
LL res = 1;
while (b > 0)
{
if (b & 1)
res = res * a % md;
a = a * a % md;
b >>= 1;
}
return res;
}
void init()
{
for (int i = 0; i < 64; i++)
{
c[i][0] = 1;
for (int j = 1; j < i; j++)
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
c[i][i] = 1;
}
}
LL cal(int num) //num个1的方案数
{ //此处取不到n,只计算了每次不取当前位
//如果每次都不取,这样就取不到n
//所以此处计算的是
LL res = 0;
for (int i = nums.size() - 1; i >= 0; i--)
{
if (nums[i]) //当前位置选取0
res += c[i][num--]; //接下来的位置中随意选取剩下的num个1
if (num < 0)
break;
}
return res;
}
void dp()
{
LL tmp = n;
nums.clear(); //初值
while (tmp)
{
nums.push_back(tmp & 1);
tmp >>= 1;
}
for (int i = nums.size(); i >= 1; i--)
ans = ans * fpow(i, cal(i)) % md;
//计算二进制中有i个1的PI
}
int main()
{
init();
cin >> T;
while (T--)
{
cin >> n;
n++; //dp求解的是小于n的,此处加1才为答案
ans = 1;
dp();
cout << ans << '\n';
}
}
#include
using namespace std;
typedef long long LL;
const int B = 66, md = 1e9 + 7;
int T;
LL n, c[B][B], a[B], dp[B][B][2];
LL QuickPow(LL a, LL b)
{
LL res = 1;
while (b)
{
if (b & 1)
res = res * a % md;
a = a * a % md;
b >>= 1;
}
return res;
}
LL dfs(int pos, int left, bool limit)
{//[高位-->低位]==[len - 1, 0],left为剩余需要枚举的1的数量
//limit表示是否受到限制
if (left == 0)
return 1;
if (pos == -1)
return 0;
auto &d = dp[pos][left][limit];
if (d != -1)
return d;
d = 0;
for (int v = 0; v <= (limit ? a[pos] : 1); v ++ )
d += dfs(pos - 1, left - (v == 1), limit && v == a[pos]);
return d;
}
LL solve()
{
LL res = 1;
int len = 0;
memset(dp, -1, sizeof dp);
memset(a, 0, sizeof a);
while (n)
a[len++] = n & 1, n >>= 1;
for (int digit = 2; digit <= len; digit ++)
res = res * QuickPow(digit, dfs(len - 1, digit, true)) % md;
//低位为0的适用性更强
return res;
}
int main()
{
cin >> T;
while (T--)
{
cin >> n;
cout << solve() << '\n';
}
}
出题人:杰
给定 n n n 个元素,其有两个属性 a i , b i a_i,b_i ai,bi,从中选择若干个元素,使得这些元素的 a a a 属性的最大公约数等于 1 1 1 ,且这些元素的 b b b 属性最大值和最小值差值最小。问这个最小值为多少?
#include
using namespace std;
const int N=5e4+10;
int T=10,tr[N*50],n;
struct node {
int a,b;
} p[N];
int cmp(node x,node y) {
return x.b<y.b;
}
int gcd(int a,int b) {
if(b==0)return a;
else return gcd(b,a%b);
}
void build(int now,int l,int r) {
if(l==r) {
tr[now]=p[l].a;
return;
}
int mid=(l+r)/2;
build(now*2,l,mid);
build(now*2+1,mid+1,r);
tr[now]=gcd(tr[2*now],tr[2*now+1]);
}
int query(int now,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return tr[now];
int mid=(l+r)/2,ans=0;
if(ql<=mid)ans=gcd(query(now*2,l,mid,ql,qr),ans);
if(qr>mid)ans=gcd(query(now*2+1,mid+1,r,ql,qr),ans);
return ans;
}
void solve(){
build(1,1,n);
int end=1,ans=1e9;
for(int str=1;str<=n;str++){
while(end<=n&&query(1,1,n,str,end)!=1)end++;
if(end<=n&&query(1,1,n,str,end)==1)ans=min(ans,p[end].b-p[str].b);
}
if(ans==1e9)cout<<-1<<endl;
else cout<<ans<<endl;
}
int main() {
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--) {
cin>>n;
for(int i=1; i<=n; i++)cin>>p[i].a;
for(int i=1; i<=n; i++)cin>>p[i].b;
sort(p+1,p+n+1,cmp);
solve();
}
return 0;
}
出题人:弛
n个字符串自由组合(可以和自己组合)问有几种结果是回文串
前言:第一个测试点靠理解题意,第四个测试点题目有描述***不同行的字符串可能相同***奇思妙想一下加上去重可以水过去,这8分比较好拿,第二个测试点的规律需要想一想,多试几个回文串的情况可以找到。
数据比较水所以很大一部分不涉及算法,想到解法暴力实现就可以。
测试点详情
第一个测试点: 常规数据,每串的长度小于20,字符串个数小于10,字符串是回文串,可以暴力通过,理解题意即可。
第二个测试点: 回文串版本的大数据,字符串个数大于1e5,字符串长度小于17 ,需要找到回文串的规律,暴力超时。
第三个测试点:非回文串的大数据,字符串个数1e4,字符串长度小于10,普遍情况,需要正解。
第四个测试点:非回文串的大数据,字符串个数小于1e4,但是相比上个测试点,有大量重复数据,可以通过去重减少字符串个数,然后暴力求解。
回文串版本:先说结论,当且只当a、b两个回文串的最小循环节一样时,a+b是回文串。
证明:假设a、b都由回文串s构成,a是x个s构成,b是y个s构成,ab拼接在一起就是x+y给s构成,此时ab的第一个s和第x+y的s对应,依次类推,如果x+y是偶数只是ssss的形式,每个s都对应最后结果仍是一个回文串。如果x+y是奇数,除了中间这个s其他s都是一一对应,中间这个s因为本身就是回文串,所以最后结果仍然是一个回文串。
暴力找到每个回文串的最小循环节后,循环节相同的拼接就是答案。
常规解法:两个字符串(a,b => ab)拼成一个回文串。情况一:a比b短,a的反串是b的后缀,同时b的剩余部分是一个回文串(比如:xyz和abazyx,xyz是zyx的反串,aba是回文串)。情况二:a,b一样长,a的反串就是b。情况三:a比b长,b的反串是a的前缀,同时a的剩余部分是回文串。
情况一和三,在判断的时候需要知道字符串的前缀或者后缀是不是一个回文串,所以先预处理出每个字符串前缀或者后缀是回文串的位置。
方法一:可以使用kmp算法,将字符串本身和他的反串进行匹配,匹配的结果就是回文串
方法二:马拉车算法,标记前缀或者后缀是回文串的位置
然后问题就变成字符串匹配了,这种多字符串匹配问题使用字典树。先把所有串放入字典树,字典树的每个节点多加一个参数就是从当前位置开始剩下部分是不是回文串,然后拿所有反串跑字典树,跑到叶子节点就看当前位置是不是回文后缀。如果字符串匹配完了,就看当前节点后面有几个回文串
细节部分比较多。标程为了防止内存溢出,把所有字符串存在了一个数组里面,然而出数据的时候长字符串的数据不好出,就没有出很长的字符串,最多才100,所以其实可以把所有字符串分开来存,不需要存在一起。
#include
using namespace std;
#define ll long long
const int N = 2000010;
char s[N];
char ss[2 * N];
int len, cnt;
int p[N * 2];
int sum[N];
bool pre[N], suf[N];
int tot;
void init(char* s) {//将每两个字符中插入一个字符
cnt = 1;
ss[0] = '!'; ss[cnt] = '#';
for (int i = 0; i < len; i++) {
ss[++cnt] = s[i], ss[++cnt] = '#';
}
ss[cnt + 1] = '#';
ss[cnt + 2] = '#';
}
void manacher(int x) {
int pos = 0, mx = 0;
for (int i = 1; i < cnt; i++) {
if (i < mx) p[i] = min(p[pos * 2 - i], mx - i);
else p[i] = 1;
while (ss[i + p[i]] == ss[i - p[i]]) p[i]++;
if (mx < i + p[i]) mx = i + p[i], pos = i;
if (p[i] == i)
pre[tot + p[i] - 2] = 1; //前缀回文
if (p[i] == cnt - i + 1)
suf[tot + sum[x] - p[i] + 1] = 1; //后缀回文
}
}
int res = 1;
struct node {
int flag;
int nex[27];
int v;//从当前开始是不是回文串
int num;
}trie[N];
void add(char* s) {
int now = 1;
for (int i = 0; i < len; i++) {
int x = s[i] - 'a';
if (trie[now].nex[x] == 0) {
trie[now].num++;
res++;
trie[now].nex[x] = res;
now = res;
if (suf[tot + i]) {
trie[now].v++;
}
}
else {
now = trie[now].nex[x];
if (suf[tot + i]) {
trie[now].v++;
}
}
}
trie[now].flag++;
}
ll find(char* s) {
int now = 1;
int i;
ll extr = 0;
for (i = len-1; i >=0; i--) {
if (pre[i + tot])extr += trie[now].flag;
int x = s[i] - 'a';
if (trie[now].nex[x] == 0)return extr;
now = trie[now].nex[x];
}
if (i == -1) {
for (int j = 0; j < 26; j++) {
extr += trie[trie[now].nex[j]].v;
}
extr += trie[now].flag;
}
return extr;
}
int main() {
int T,n;
scanf("%d",&T);
for(int p=0;p<T;p++){
scanf("%d",&n);
for (int i = 0; i <= res+2; i++) {
trie[i].flag = trie[i].num = trie[i].v = 0;
memset(trie[i].nex, 0, sizeof(trie[i].nex));
}
res = 1;
memset(pre,0,sizeof(pre));
memset(suf,0,sizeof(suf));
tot = 0;
for (int i = 1; i <= n; i++) {
scanf("%d %s", &sum[i], s + tot);
len = sum[i];
init(s + tot);
manacher(i);
len = sum[i];
add(s + tot);
tot += sum[i];
}
ll an = 0;
tot = 0;
for (int i = 1; i <= n; i++) {
len = sum[i];
an += find(s + tot);
tot += sum[i];
}
printf("%lld\n", an);
}
}
出题人:贝
总体思路为:贪心+区间 D P DP DP+位运算+前缀和+二分
设最后的答案为ans
区间 D P DP DP状态设计+位运算思想:当前验证res
是否为ans
的二进制的前若干位,f[i][j]
表示[1,i]
区间划分成为j
份,是否存在解,如果存在则为true
,否则为false
。特殊的有(根据状态含义),f[0][j]
等于false
, 1 ≤ j ≤ n 1\leq j \leq n 1≤j≤n,且初始f[0][0]
等于true
贪心+二分思想: 从二进制的高位往低位贪心,二分ans
当前位的结果,check()
函数的返回值为1
,说明ans
当前位为1
,否则为0
区间 D P DP DP决策:显然,将[1,i]
分为j
份,的前一个状态就是由另一个区间分解为j-1
份得来的,即[1,k]
,其中 0 ≤ k < i 0\leq k< i 0≤k<i,然后再与[k+1,j]
的区间和做按位与,若按位于的结果中包含当前的res
,则即可由f[k][j-1]
转移到f[i][j]
区间 D P DP DP状态转移方程:易得只要[1,k]
中存在一种方案使得其为true
则最后的f[i][j]
就为true
(其中 0 ≤ k < i 0\leq k< i 0≤k<i),所以用的就是或的性质。状态转移方程为:f[i][j] = f[i][j] | f[k][j-1]
区间 D P DP DP转移顺序:虽然这边我已经用字母表的顺序标记出来,三层for
循环的顺序应该为i-j-k
,但是如果现场推的时候,你设计的变量不会刚刚好是这三个,考虑转移的顺序,也往往容易再众多 d p dp dp教程中被忽略掉。所有的f[k][j-1]
都应该在f[i][j]
之前被计算出来,并且 0 ≤ k < i 0\leq k < i 0≤k<i,所以k
受到i
的限制,k
在i
的内层循环。并且j
受到i
的限制,你分的份数不能超过子数组的长度,所以j
也在i
的内层循环。j
和k
相互不限制,所以转移的循环顺序i-j-k
或i-k-j
都是可行的。
1LL<这样写才不会爆
LL
范围,最大为1LL<<50
。但是中间应用到了sum
和,n
为 50 50 50,所以从 60 60 60开始保险(貌似我数据还没改,等我没有那么忙了就加一下)
#include
using namespace std;
typedef long long LL;
const int N = 55;
int T, n, m;
LL a[N], sum[N];
bool f[N][N];
bool check(LL res) //二分思想
{
memset(f, 0, sizeof f);
f[0][0] = true;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i and j <= m; j++) //分成j份
for (int k = 0; k < i; k++)
if ((sum[i] - sum[k] & res) == res) //[k+1,i]这个区间是否满足sum[k+1,i]&res=res
f[i][j] |= f[k][j - 1]; //f[k][j-1]存在的情况才可以转移过去,只有0,1的dp
return f[n][m];
}
int main()
{
cin.tie(0);
cout.tie(0);
cin >> T;
while (T--)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i], sum[i] = sum[i - 1] + a[i];
LL res = 0;
for (int i = 60; i >= 0; i--) //高位到低位贪心
{
res |= 1LL << i;
if (!check(res))
res ^= 1LL << i;
}
cout << res << '\n';
}
return 0;
}
出题人:杰
给定 n n n 个点的一棵树,每个结点最多可以放累计高度为 12 12 12 的弹珠,初始时每个点的累计高度值均为 0 0 0。 m m m 次操作,每次操作给定 x i , y i , h i x_i,y_i,h_i xi,yi,hi ,如果从 x i x_i xi 到 y i y_i yi 的简单路径上有可以放入 h i h_i hi 的点,则第一个可以放入的点累计高度加上 h i h_i hi 。 m m m 次操作后,求每个结点累计了多少高度的弹珠。
暴力送10分
对于 x , y x,y x,y 这条路径,直接跑 x x x 到 l c a lca lca,找到第一个满足的点;没有的话,再跑 y y y 到 l c a lca lca ,找到最后一个满足的点即可。
以下为部分代码,前置知识是 l c a lca lca
void solve(int x,int y,int w){
int t=-1;
while(x!=y){
if(dep[x]>=dep[y]){//x往上跳
if(ans[x]+w<=12){
ans[x]+w;
return;
}
x=f[x];
}else{//y往上跳
if(ans[y]+w<=12)t=y;
y=f[y];
}
}
if(ans[x]+w<=12){//lca位置
ans[x]+=w;
return;
}
if(t!=-1)ans[t]+=w;
}
树链剖分+线段树二分
树链剖分部分:
树链剖分之后,任意两个点的路径均在 l o g n logn logn 个线段树上的区间内了。
每次给定 x , y x,y x,y ,我们将这 l o g n logn logn 个区间离线,依次对每个线段树二分,找到第一个满足的点即可。
但是需要注意的是,如果是从 x x x 往上跑重链的话,应该是在线段树上该重链所对应区间,靠右二分。如果是从 y y y 往上跑重链的话,应该是在线段树上该重链所对应区间,靠左二分。
因此对于是 x x x 往上跳重链的区间,我们记录在 q 1 q1 q1 中;对于是 y y y 往上跳重链的区间,我们记录在 q 2 q2 q2 中。
最后正着跑 q 1 q1 q1 中的区间,线段树靠右二分找到第一个满足条件的点。找不到就倒着跑 q 2 q2 q2 中的区间,线段树靠左二分找到第一个满足条件的点。
线段树二分部分:
线段树建树时,将左右结点初始化成 12 ,并维护区间最大值(表示,对于一个弹珠发射,该区间最多可以放多少高度的弹珠),那么区间最大值是否 > = h i >=h_i >=hi 就表示区间内是否有满足条件的点。
如果是靠左二分:就优先判断作左区间是否有满足条件的点,有就递归到左子区间查,没有就判断右区间是否有满足条件的点,有就递归右子区间查,还是没有就表示整个区间内没有满足条件的点了。
#include
using namespace std;
const int N=1e6+10;
struct E {
int u,v,next;
} e[N*2];
int vex[N],tot,dep[N],id[N],seg[N],cnt,top[N],f[N],tr[N*40],siz[N],son[N],n,m;
struct Q {
int l,r;
} q1[N],q2[N];
void add(int u,int v) {
tot++;
e[tot].u=u;
e[tot].v=v;
e[tot].next=vex[u];
vex[u]=tot;
}
void build(int now,int l,int r) {
if(l==r) {
tr[now]=12;
return;
}
int mid=(l+r)/2;
build(now*2,l,mid);
build(now*2+1,mid+1,r);
tr[now]=max(tr[now*2],tr[now*2+1]);
}
int query(int now,int l,int r,int ql,int qr,int w,int key) {//key=1,表示靠右二分;key=2,表示靠左二分
if(l==r) {
if(tr[now]>=w) {
tr[now]-=w;
return 1;
} else return 0;
}
int mid=(l+r)/2,t=0;
if(ql<=l&&r<=qr) {
if(key==1) {
if(tr[now*2+1]>=w) {
t=query(now*2+1,mid+1,r,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
return t;
} else {
t=query(now*2,l,mid,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
return t;
}
} else {
if(tr[now*2]>=w) {
t=query(now*2,l,mid,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
return t;
} else {
t=query(now*2+1,mid+1,r,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
return t;
}
}
}
if(key==1) {
if(qr>mid)t=query(now*2+1,mid+1,r,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
if(t==1)return 1;
if(ql<=mid)t=query(now*2,l,mid,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
if(t==1)return 1;
else return 0;
} else {
if(ql<=mid)t=query(now*2,l,mid,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
if(t==1)return 1;
if(qr>mid)t=query(now*2+1,mid+1,r,ql,qr,w,key);
tr[now]=max(tr[now*2],tr[now*2+1]);
if(t==1)return 1;
else return 0;
}
tr[now]=max(tr[now*2],tr[now*2+1]);
}
int queryvalue(int now,int l,int r,int x) {//单点查询
if(l==r)return tr[now];
int mid=(l+r)/2;
if(x<=mid)return queryvalue(now*2,l,mid,x);
else return queryvalue(now*2+1,mid+1,r,x);
}
void predfs(int u,int fa) {//树链剖分预处理第一遍dfs
f[u]=fa;
dep[u]=dep[fa]+1;
siz[u]=1;
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
if(v==fa)continue;
predfs(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
void redfs(int u,int fa,int root) {//树链剖分预处理第二遍dfs
id[u]=++cnt;
seg[cnt]=u;
top[u]=root;
if(son[u]!=0)redfs(son[u],u,root);
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
if(v==fa||v==son[u])continue;
redfs(v,u,v);
}
}
void solve(int x,int y,int w) {
int key=1;//key=1,表示是最初的x在往上跳重链; key=2,表示是最初的y在往上跳重链;
int top1=0,top2=0;
while(top[x]!=top[y]) { //不在一条重链上
if(dep[top[x]]<dep[top[y]]) {//x为所在重链较深的点
swap(x,y);
key=-key;
}
if(key==1) {
top1++;
q1[top1].l=id[top[x]];
q1[top1].r=id[x];
} else {
top2++;
q2[top2].l=id[top[x]];
q2[top2].r=id[x];
}
x=f[top[x]];
}
if(dep[x]<dep[y]) {
swap(x,y);
key=-key;
}
if(key==1) {
top1++;
q1[top1].l=id[y];
q1[top1].r=id[x];
} else {
top2++;
q2[top2].l=id[y];
q2[top2].r=id[x];
}
for(int i=1; i<=top1; i++) {
int t=query(1,1,n,q1[i].l,q1[i].r,w,1);
if(t==1)return;
}
for(int i=top2; i>=1; i--) {
int t=query(1,1,n,q2[i].l,q2[i].r,w,2);
if(t==1)return;
}
}
int main() {
//freopen("5.in","r",stdin);
//freopen("5.out","w",stdout);
cin.tie(0);
cout.tie(0);
int u,v,w;
cin>>n>>m;
for(int i=1; i<n; i++) {
cin>>u>>v;
add(u,v);
add(v,u);
}
predfs(1,0);
redfs(1,0,1);
build(1,1,n);
for(int i=1; i<=m; i++) {
cin>>u>>v>>w;
solve(u,v,w);
}
for(int i=1; i<=n; i++)cout<<12-queryvalue(1,1,n,id[i])<<'\n';
return 0;
}