济南 S NOIP 刷题实战梳理营游记(下)
Day 1 ~ 6
https://www.cnblogs.com/OoXiaoQioO/p/17577063.html
Day 7
字符串专题。
\(50+60+0+0 = 110\) pts。Rank 23/58。
答辩题。
A 咒语
给定一个初始串 s,咒语是由 s 无限循环得到的,如 s 为 aab,那么咒语为 \(\verb!aabaabaab...!\)
接着国王每次选择一个区间 \(L,R\),复制第 \(L\) 个字母到第 \(R\) 个字母构成的子串,并把它插入到第 \(C\) 个字母后,特殊的,当 \(C=0\) 时代表插到了开头。
请你求出操作后第 \(k\) 个字母是什么?
数据范围:\(1\le n,m\le 10^6,1\le k,L_i,R_i,C_i \le 10^{15}\)。
输入格式
第一行三个整数 \(n,m,k\) 表示字符串长度,操作次数,和所求位置。
第二行一个字符串 s 表示初始串。
接下来 m 行,每行三个整数 \(L_i,R_i,C_i\) 表示一次操作。
输出格式
一行一个字符表示答案。
样例输入:
5 3 6
puyhm
1 9 0
3 3 6
1 3 2
样例输出:
7
考场思路
简单的 string 的操作。
先科普一下 string 的基本操作:
s.size()
为 \(s\) 的长度。s.insert(x,y)
在 \(x\) 的位置插入 \(y\),\(y\) 是一个字符串。s.substr(l,i)
截取 \(s\) 中 \([l,l+i]\) 的字符串,而不是 \([l,i]\) 的!
然后就是简单模拟了。
代码(\(50\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
int n,m;
ll k;
string s;
int main()
{
// freopen("a_1.in","r",stdin);
cin>>n>>m>>k>>s;
while(s.size()<100000)
s += s;
while(m--)
{
int l,r,c;
read(l,r,c);
string ss = s.substr(l-1,r-l+1);
// cout<<ss<<endl;
if(c==0)
s = ss+s;
else
s.insert(c,ss);
// cout<<s<<endl;
}
cout<<s[k-1]<<endl;
return 0;
}
正解
倒序考虑。
- 如果在复制后位置的前面,则此次操作没用。
- 如果在里面,则换到对应位置。
- 如果在后面,减去复制的长度即可。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
ll n,m,k,l[1000010],r[1000010],c[1000010];
char s[10000010];
int main()
{
read(n,m,k);
scanf("%s",s+1);
int i;
for(i=1;i<=m;i++)
read(l[i],r[i],c[i]);
for(i=m;i>=1;i--)
{
int len = r[i]-l[i]+1;
int L = c[i]+1;
int R = c[i]+len;
if(L<=k&&k<=R)
k += l[i]-L;
else if(k>R)
k -= len;
}
putchar(s[(k-1)%n+1]);
return 0;
}
B 博得
题意
定义 \(s\sim t\) 当且仅当排序后 \(s=t\)。
终于小 z 找来一个足够长的字符串 S,他想在新的等价关系下讨论 S 的 Border,\(S[l,r]\) 表示 \(\verb!S!\) 字符串 \(l\) 到 \(r\) 位置构成的字串,他定义了 \(border(S)\) 为最大的 \(r < |S|\) 使得 \(S[1,r] \sim S[n-r+1,n]\)。特殊地,如果 \(|S| = 1\),那么 \(border(S) = 0\)。
但是他没办法算出这样长串的 Border,请你帮帮他,完全利用 \(\verb!S!\) 的研究价值,算出 \(\verb!S!\) 每一个前缀的 Border 吧!
数据范围:\(|S|\le 10^6\)。
输入格式
一行一个串 \(\verb!S!\)。
输出格式
一行一个整数表示 \(\verb!S!\) 所有前缀的 Border 之和。
样例输入
ababa
样例输出
10
考场思路
模拟。
QBLT 的答辩机子已经不想多说了,同一份代码。\(50、60、70\) 分。
代码(\(60\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
string s,t;
int l;
int vis[31];
bool check(string a,string b)
{
int l = a.size();
memset(vis,0, sizeof(vis));
int i;
for(i=0;i<l;i++)
{
if(vis[a[i]-'a'+1]!=b[i]-'a'+1&&vis[a[i]-'a'+1]!=0)
return 0;
if(vis[b[i]-'a'+1]!=a[i]-'a'+1&&vis[b[i]-'a'+1]!=0)
return 0;
vis[a[i]-'a'+ 1] = b[i]-'a'+1;
vis[b[i]-'a'+ 1] = a[i]-'a'+1;
}
return 1;
}
long long ans;
int main()
{
// freopen("b_2.in","r",stdin);
cin>>s;
l = s.size();
int i,j;
for(i=0;i<l;i++)
{
t = s.substr(0,i+1);
int tl = i+1;
// cout<<"i:"<<i<<endl<<"t:"<<t<<endl<<"tl:"<<tl<<endl;
for(j=tl-1;j>=1;j--)
{
// cout<<"t1:"<<t1<<endl<<"t2:"<<t2<<endl;
if(check(t.substr(0,j),t.substr(tl-j,tl)))
{
ans += t.substr(0,j).length();
break;
}
}
}
cout<<ans<<endl;
return 0;
}
正解
考虑如何判断两个串相等?
- 记录每个字符和这个字符上次出现位置的距离,变成新的数组。
- 两个字符串相等当前仅当新的数组相同。
- 利用此性质做 KMP 就行了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
char s[1000010];
int f[1000010],nxt[1000010],T[114514],n;
ll ans;
void KMP()
{
int i,j = 0;
for(i=2;i<=n;i++)
{
while(j&&f[j+1]!=min(f[i],j+1))
j = nxt[j];
if(f[j+1]==min(f[i],j+1))
j++;
nxt[i] = j,ans += j;
}
return;
}
int main()
{
scanf("%s",s+1);
n = strlen(s+1);
int i;
for(i=1;i<=n;i++)
f[i] = i-T[s[i]-'a'],T[s[i]-'a'] = i;
KMP();
cout<<ans<<endl;
return 0;
}
C 能量水晶
题意
给定一个长度为 \(n\) 的正整数序列,求有多少个区间满足其元素的乘积开 \(k\) 次根号为整数。
数据范围:\(1\le n\le 10^5,1\le a_i \le 10^7,1\le k\le 6\)。
输入格式
第一行包含两个整数 n 和 k,分别表示序列的长度和开根次数。
第二行包含 n 个正整数 \(a_i\),表示序列中的元素。
输出格式
第一行包含两个整数 \(n\) 和 \(k\),分别表示序列的长度和开根次数。
第二行包含 \(n\) 个正整数 \(a_i\),表示序列中的元素。
样例输入
5 2
1 4 2 8 16
样例输出
10
考场思路
没时间写了。
正解
考虑一段区间可以开 \(k\) 此根号当且仅当区间乘起来以后每个质因子的幂次都是 \(k\) 的倍数。
维护一个串,\(s_i\) 表示为第 \(i\) 个指数的幂此膜 \(k\) 的玉树,当这个串都为 \(0\) 的时候显然可以开 \(k\) 次根号。
考虑哈希维护这个串,用 map 存一下左端点,在右端点处查询即可。
注意用自然溢出而不是普通哈希。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
map<pair<ll,int>,int> m;
int n,k;
int e[100000010],pr[100000010],a[100000010];
int num;
int v[100000010];
ll mi[10000010];
int m1[10000010];
const int p = 1e9+9;
void init()
{
mi[0] = m1[0] = 1;
int i,j;
for(i=2;i<=10000001;i++)
{
if(!e[i])
e[i] = ++num,pr[e[i]] = i;
for(j=1;j<=e[i]&&pr[j]*i<=10000001;j++)
e[pr[j]*i] = j;
}
for(i=1;i<=num;i++)
mi[i] = mi[i-1]*27ull,m1[i] = m1[i-1]*137ull%p;
}
int main()
{
cin>>n>>k;
init();
ll all,ans,a2;
all = ans = a2 = 0;
m[{all,a2}] = 1;
int i,x;
for(i=1;i<=n;i++)
{
cin>>x;
while(x!=1)
{
int t = e[x];
all = (all+mi[t]);
a2 = (a2+m1[t])%p;
if(++v[t]==k)
{
v[t] = 0,all = (all-mi[t]*k);
a2 = (a2-1ll*m1[t]*k%p+p)%p;
}
x /= pr[t];
}
ans += m[{all,a2}]++;
}
cout<<ans<<endl;
return 0;
}
D 字符串谷
题意
游戏规则如下:
在游戏开始时,有 \(n\) 个 01 字符串。Alice 和 Bob 轮流从这些字符串中选择一个,直到所有的字符串都被选完。当 Alice 和 Bob 选择了一些字符串后,她可以计算这些字符串组成的集合 \(\verb!S!\) 的价值。价值由集合中字符串的权值和集合中所有字符串对的最长公共前缀(Lcp)之和决定,但不包括字符串本身与自己的对。为了取得胜利,双方都希望自己的价值减去对方的价值最大。
作为这个国度的智者,你的任务是计算出在双方都采取最优策略的情况下,这个价值差是多少。
数据范围:\(1\le n\le 10^4,1\le \sum |s_i| \le 10^7,1\le v_i \le 10^8\)。
输入格式
输入的第一行包含一个整数 \(n\),表示字符串的数量。
接下来的 \(n\) 行,每行包含一个由 01 组成的字符串和一个一个权值 \(a_i\)。
输出格式
输出一个整数,表示在双方都采取最优策略的情况下的价值差。
样例输入:
2
100 4
100 0
样例输出:
4
考场思路
没时间了,草草看一眼觉得像 DFS。
Day 8
图论+DP 专题。
\(100+80+0+0=180\) pts。Rank 24/58。
A 链条
题意
给定一棵带点权的树,第 \(i\) 个点的点权为 \(v_i\)。
一条链 \(a_1,a_2,\cdots,a_k\) 的权值定义为 \(a_1\oplus a_2 + a_2 \oplus a_3 + \cdots + a_{k-1} \oplus a_k\)。
求树上最大权值的一条链的权值。
数据范围:\(f_i<i,1\le n\le 10^5,0\le a_i \le 1000\)。
输入格式
第一行一个整数表示节点个数
第二行 \(n\) 个整数,第 \(i\) 个数表示节点权值 \(a_i\)。
第三行 \(n - 1\) 个整数,第 \(i\) 个整数表示点 \(i + 1\) 的父亲节点 \(f_{i+1}\),保证 \(f_{i} < i\)。
输出格式
一行一个整数表示答案。
样例输入:
5
0 6 0 4 7
1 1 1 3
样例输出:
13
考场思路
打眼一看是个 Trie 树,但运用人类心理学感觉 A 题不会考这玩意。
深入想了想发现是树的直径。只要把加法运算改成异或就好了。
注意一下朴素的树的直径是基于边的,这里是点,改一下就好了。
代码(\(100\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100001,M = N<<1;
struct node
{
int to,nxt,w;
}e[M];
int cnt,head[N];
int w[100001];
void add(int u,int v,int w)
{
cnt++;
e[cnt].to = v;
e[cnt].nxt = head[u];
e[cnt].w = w;
head[u] = cnt;
}
int ans,n;
int d1[N],d2[N];
int dfs(int u,int father)
{
int dis = 0,i;
d1[u] = d2[u] = 0;
for(i=head[u];i;i=e[i].nxt)
{
int v = e[i].to;
if(v==father)
continue;
dfs(v,u);
int d = d1[v]+e[i].w;
if(d>=d1[u])
d2[u] = d1[u],d1[u] = d;
else if(d>d2[u])
d2[u] = d;
}
ans = max(ans,d1[u]+d2[u]);
return dis;
}
int main()
{
cin>>n;
int i;
for(i=1;i<=n;i++)
cin>>w[i];
for(i=2;i<=n;i++)
{
int father;
cin>>father;
add(father,i,w[i]^w[father]);
add(i,father,w[i]^w[father]);
}
dfs(1,0);
cout<<ans<<endl;
return 0;
}
正解
同思路。
B 奇迹之地
题意
一个 DAG,对于点对 \((x,y)\),\(x\ne y\),且从 \(x\) 到 \(y\) 的不同路径的个数是奇数,就是一对合法点对。
求有多少的合法点对。
输入格式
第一行:两个整数 \(n\) 和 \(m\),表示房间的数量和单向通道的数量。
接下来的 \(m\) 行:每行两个整数 \(u\) 和 \(v\),表示从房间 \(u\) 到房间 \(v\) 有一条单向通道。
输出格式
输出一个整数,表示满足条件的房间对数量。
样例输入
4 5
3 1
4 1
3 4
3 2
4 1
样例输出
3
考场思路
拓扑序的简单运用,但是 \(80\) 分,正解竟然需要 bitset /px/px
放一下代码吧(\(80\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 50005;
struct edge
{
int to, nxt;
}e[maxn];
int head[maxn],tpcnt,deg[maxn],tp[maxn];
int cnt;
inline void add(int u, int v)
{
++cnt;
e[cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt;
}
queue<int> q;
int n, m;
void tuopu()
{
int i;
for(i=1;i<=n;i++)
if(!deg[i])
q.push(i);
while(!q.empty())
{
int u = q.front();
q.pop();
tp[++tpcnt] = u;
for(i=head[u];i;i=e[i].nxt)
{
int v = e[i].to;
deg[v]--;
if(deg[v]==0)
q.push(v);
}
}
return;
}
int dp[maxn];
int count(int t)
{
int ans = 0;
memset(dp,0,sizeof(dp));
dp[t] = 1;
int i,j;
for(i=n;i>=1;i--)
{
int u = tp[i];
for(j=head[u];j;j=e[j].nxt)
{
int v = e[j].to;
dp[u] += dp[v];
}
}
for(i=1;i<=n;i++)
ans += (dp[i] & 1) == 1;
return ans;
}
int main()
{
// freopen("b_1.in","r",stdin);
cin>>n>>m;
int i;
for(i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
deg[v]++;
}
tuopu();
int ans = 0;
for(i=1;i<=n;i++)
ans += count(i);
cout<<ans-n<<endl;
return 0;
}
正解
也和拓扑排序有关。
考虑设 \(f_{x,y}\) 表示从 \(y\) 出发到 \(x\) 的路径条数,可以用拓扑排序并转移:
若 \(z\) 是 \(x\) 的一个前缀,那么 f_{x,y} += f_{z,y}
。
由于我们记录奇偶性,所以 \(f_{x,y}\) 只需要记录 01 即可,那么发现转移就是 \(f_{x,y}=f_{x,y}\oplus f_{z,y}\),用 bitset 维护就可以了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
queue<int> q;
vector<int> g[50001];
bitset<50001> dp[50001];
int deg[50001],n,m;
ll ans;
int main()
{
read(n,m);
int i;
for(i=1;i<=m;i++)
{
int u,v;
read(u,v);
g[u].push_back(v);
deg[v]++;
}
for(i=1;i<=n;i++)
{
dp[i][i] = 1;
if(!deg[i])
q.push(i);
}
int cnt = 0;
while(!q.empty())
{
cnt++;
int u = q.front();
q.pop();
ans += dp[u].count()-1;
for(auto v:g[u])
{
dp[v] ^= dp[u];
if(!--deg[v])
q.push(v);
}
}
printen(ans);
return 0;
}
C 星树
题意
在一个遥远的星球上,有一种神奇的生物叫做“星树”,星树的生长过程非常特殊。星树的每个叶子节点都可以赋予一个权。在这个星球上,有 \(k\) 个叶子节点,星树的智者们有 \(k\) 个神秘的权值石,对应的权值为 \(1\) 到 \(k\),智者中的长老已经分配一些叶子的权值,他们需要把剩下权值石分配给其他的个叶子节点。
星树的每个非叶子节点的权值取决于其子节点的权值,具体来说,非叶子节点的权值等于其子节点权值的中位数(具体地,如果有 \(k\) 个孩子,那么权值为排名 \(\lceil \frac k2 \rceil\) 的孩子权值)。星树的智者们希望通过合理分配权值石,使得星树的根节点的权值达到最大。
你作为星树的智者,需要找到一种分配方案,使得根节点的权值最大。
数据范围:\(1\le n,m\le 2\times 10^5,f_i \le i\)。
输入格式
第一行输入两个整数 \(n, k\),表示星树的节点数量和叶子个数(保证编号为 \(n - k + 1\) 到 \(n\) 的节点为叶子节点,其余节点为非叶子节点)。
接下来一行 \(n - 1\) 个整数 \(f_{i + 1}\),表示 \(i + 1\) 的父亲节点为 \(f_{i + 1}\)。
接下来一行 \(k\) 个整数,表示 \(n - k + 1\) 到 \(n\) 这些叶子节点的权值(\(0\) 表示未分配)。
输出格式
输出一个整数,表示分配权值石后,根节点的最大权值。
样例输入:
8 5
1 1 3 2 3 3 3
3 0 0 2 0
样例输出:
3
考场思路
不会啊!!!
看到有不错的暴力分,但是懒得写了(其实去调 B 了)。
无代码。
正解
可以二分答案 \(min\),检查是否存在合法方案使得根节点答案 \(\ge mid\)。
设 \(f_x\) 表示最少在 \(x\) 的子树内分配多少个 \(\ge mid\) 的叶子权值,使得 \(x\) 的权值 \(\ge mid\)。
那么就是选择 \(\frac{k}{2}\) 个儿子节点,那么 \(f_x = \sum f_y\)。所以将儿子按照 \(f_x\) 排序,从小到大排序,最终判断 \(f_x\) 是否 \(\le\) 可分配的叶子权值个数即可。
代码:
点击查看代码
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,k,ans=1,L=1,R,head[N],to[2*N],nedge,Next[2*N],a[N];
long long dp[N];
void add(int a,int b){Next[++nedge]=head[a],head[a]=nedge,to[nedge]=b;}
void dfs(int u,int x){
if(u>=n-k+1){
dp[u]=(a[u]?(a[u]>=x?0:0x3f3f3f3f):1);
return;
}
vector<long long>t;
for(int i=head[u];i;i=Next[i]){
dfs(to[i],x);
t.push_back(dp[to[i]]);
}
sort(t.begin(),t.end());
int k=(int)t.size()/2+1;
for(int i=0;i<k;i++)dp[u]+=t[i];
}
bool check(int x){
int d=k-x+1;
for(int i=n-k+1;i<=n;i++)if(a[i]>=x)d--;
memset(dp,0,sizeof dp);
dfs(1,x);
return d>=dp[1];
}
signed main(){
cin>>n>>k;
R=k;
for(int i=2;i<=n;i++){
int u;
cin>>u;
add(u,i);
}
for(int i=n-k+1;i<=n;i++)cin>>a[i];
while(L<=R){
int mid=(L+R)>>1;
if(check(mid))ans=mid,L=mid+1;
else R=mid-1;
}
cout<<ans;
return 0;
}
千叶树
题意
在一个遥远的神秘世界,有一个名为“千叶树”的神奇生物。千叶树拥有 \(n\) 个节点,其中 \(1\) 号节点为根。传说在这个世界中,有一种强大的魔法能量,能够将千叶树的节点分成 \(m\) 个非空集合,但这种魔法力量有一个特殊的限制:不能将具有祖孙关系的两个节点划分到同一个集合中。两种方案不同当且仅当存在两个点 \(x,y\) 满足第一种方案里在一个集合而第二种方案不在。
作为这个世界的探险家,你的任务是找出有多少种方案将千叶树的 \(n\) 个节点划分成 \(m\) 个非空集合,同时满足祖孙关系的节点不在同一个集合中。
对于点 \(x,y\),如果 \(x\) 在 \(y\) 的子树内,那么称 \(y\) 是 \(x\) 的祖先。\(x,y\) 具有祖孙关系当且仅当 \(x\) 是 \(y\) 的祖先或 \(y\) 是 \(x\) 的祖先。
数据范围:\(1\le n\le 10^5,1\le m\le 100,f_i < i\)。
输入格式:
第一行:两个整数 \(n\) 和 \(m\),表示千叶树的节点数量和要划分的集合数量。
接下来一行 \(n-1\) 个整数,第 \(i\) 个整数表示 \(i+1\) 的父亲节点为 \(f_{i+1}\)
输出格式:
输出一个整数,表示满足条件的划分方案数量。答案对 \(10^9 + 7\) 取模。
样例输入:
4 3
1 1 1
样例输出:
3
考场思路
不会,无代码。
正解
考虑从上到下分配集合,或者按照 DFS 序分配,保证祖先在自己之前分配集合即可。
当 \(y\ge dep\) 时,那么有 \(x_{x,y} = f_{x-1,y}\times (y-dep)+f_{x-1,y-1}\)。
这代表放到之前的集合内(不能和祖先放在一起,其他的集合任意放),或者自己新建一个集合。
代码:
点击查看代码
#include<bits/stdc++.h>
#define mod 1000000007
#define N 100005
#define ll long long
using namespace std;
int head[N],Next[N],to[N],nedge,cnt,m,n,dp[N][105];
void add(int a,int b){Next[++nedge]=head[a],head[a]=nedge,to[nedge]=b;}
void dfs(int u,int dep){
cnt++;
for(int i=dep;i<=m;i++)dp[cnt][i]=(1ll*dp[cnt-1][i]*(i-dep+1)+dp[cnt-1][i-1])%mod;
for(int i=head[u];i;i=Next[i])dfs(to[i],dep+1);
}
int main(){
cin>>n>>m;
dp[0][0]=1;
for(int i=2;i<=n;i++){
int u;
cin>>u;
add(u,i);
}
dfs(1,1);
cout<<dp[cnt][m];
return 0;
}
Day 9
搜索专题(bydtm,csctr!一共四题,两道纯打表题,乐)。
\(100+10+0+100\) pts。Rank 16/58。
A 代数
题意
\(n\) 个数 \(v_1,v_2,\cdots,v_n\)。要把这 \(n\) 个数按照某个顺序分给 \(n\) 个未知数,使得代数式的值为 \(m\),判断是否可行。多厕。
数据范围:\(1\le n\le 5,0\le v_i \le 50,0\le m\le 10^9,T\le 1000\)。
输入格式
第一行一个正整数 \(T\), 表示测试数据组数。
每组测试数据第一行一个正整数 \(n\), 第二行 \(n\) 个正整数 \(v_1,v_2,\cdots,v_n\), 第三行一个正整数 \(m\), 第四行一个字符串表示代数式。
保证代数式满足 \((e_1\ op \ e_2)\) 的形式, 其中 \(e_1,e_2\) 为代数式或者单个未知数, \(op\) 为 \(+,-,*\) 之一的运算符。
输出格式
对于每组测试数据,输出一行YES
或NO
表示是否可行。
样例输入:
3
3
2 3 4
14
((a+b)*c)
2
4 3
11
(a-b)
1
2
2
a
样例输出:
YES
NO
YES
考场思路
这题太典了啊!
简单的表达式求值,用栈或递归求解,考场上我用的是栈。
枚举每个未知数的值时可以使用 next_permutation(v+1,v+1+n)
,然后用 map
存一下即可。
代码(\(100\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
ll t,n,m;
ll v[114];
map<char,int> num; //每次全排列,模拟 char 的 value
char letters[1145];//存 s 中的字母
int letters_cnt;//有多少个字母
int yssx(char op)
{
if (op == '+' || op == '-')
return 1;
else if (op == '*' || op == '/')
return 2;
else
return 0;
}
string solve1(string S)
{
stack<char> ope;
string houzhui;
for (char c : S)
{
if (isdigit(c) || isalpha(c))
houzhui += c;
else if (c == '(')
ope.push(c);
else if (c == ')')
{
while (!ope.empty() && ope.top() != '(')
{
houzhui += ope.top();
ope.pop();
}
ope.pop(); // 弹出左括号 '('
}
else
{ // 运算符
while (!ope.empty() && yssx(ope.top()) >= yssx(c))
{
houzhui += ope.top();
ope.pop();
}
ope.push(c);
}
}
while (!ope.empty())
{
houzhui += ope.top();
ope.pop();
}
return houzhui;
}
int solve2(string houzhui)
{
stack<int> oper;
for (char c : houzhui)
{
if (isdigit(c))
oper.push(c - '0');
else if (isalpha(c))
oper.push(num[c]);
else
{
int a2 = oper.top();
oper.pop();
int a1 = oper.top();
oper.pop();
if (c == '+')
oper.push(a1 + a2);
else if (c == '-')
oper.push(a1 - a2);
else if (c == '*')
oper.push(a1 * a2);
else if (c == '/')
oper.push(a1 / a2);
}
}
return oper.top();
}
int main()
{
// freopen("sample_2.in","r",stdin);
// freopen("oo.out","w",stdout);
cin>>t;
string s;
while(t--)
{
letters_cnt = 0;
int i;
cin>>n;
for(i=1;i<=n;i++)
cin>>v[i];
sort(v+1,v+1+n);
cin>>m;
cin>>s;
int l = s.size();
for(i=0;i<l;i++)
if(s[i]>='a'&&s[i]<='z')
letters[++letters_cnt] = s[i];
int flag = 0;
//其实 letters_cnt = n
do
{
num.clear();
//未知数位置不动,让权值全排列
for(i=1;i<=n/*letter_cnt*/;i++)
num[letters[i]] = v[i];
string houzhui = solve1(s);
ll ans = solve2(houzhui);
if(ans==m)
{
puts("YES");
flag = 1;
break;
}
}while(next_permutation(v+1,v+n+1));
if(!flag)
puts("NO");
}
return 0;
}
正解
同思路。
B KAKURASU
老师说他是在玩这个游戏的时候出的此题。
题意
游戏的规则非常简单,给定一个 \(n\times m\) 的方格, 你需要给每个方格染黑色或白色。 第 \(i\) 行第 \(j\) 列的格子如果被染成黑色会给第 \(i\) 行贡献 \(j\) 的点数,给第 \(j\) 列贡献 \(i\) 的点数,如果染成白色则不会贡献任何点数。
最后要求第 \(i\) 行总点数为 \(r_i\), 第 \(j\) 列总点数为 \(c_j\), 你需要给出一种满足条件的染色方法。
数据范围:\(1\le n,m\le 15,T\le 5\)。保证有解。
输入格式
第一行一个正整数 \(T\), 表示测试数据组数。
每组测试数据第一行两个正整数 \(n,m\) 表示方格大小。
第二行 \(n\) 个数 \(r_1,\cdots,r_n\) 表示行的点数限制。
第三行 \(m\) 个数 \(c_1,\cdots, c_m\) 表示列的点数限制。
输出格式
输出 \(n\times m\) 个 \(0/1\) 表示每个位置是否染色。
样例输入:
1
3 3
5 4 3
5 4 3
样例输出:
0 1 1
1 0 1
1 1 0
考场思路
简单的 DFS。
真的不想吐槽 QBLT 的答辩机子了。
代码(\(10\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int t,n,m;
int r[21],c[21];
int a[21][21];
bool vis[21][21];
int calc_row(int x)
{
int sum = 0;
int i;
for(i=0;i<m;i++)
if(a[x][i])
sum += (i+1);
return sum;
}
int calc_col(int x)
{
int sum = 0;
int i;
for(i=0;i<n;i++)
if(a[i][x])
sum += (i+1);
return sum;
}
bool check()
{
int i,j;
for(i=0;i<n;i++)
if(calc_row(i)!=r[i])
return 0;
for(i=0;i<m;i++)
if(calc_col(i)!=c[i])
return 0;
return 1;
}
bool dfs(int row,int col)
{
if(row==n)
return check();
if(col==m)
return dfs(row+1,0);
if(row>r[row])
return 0;
if(col>c[col])
return 0;
a[row][col] = 1;
if(dfs(row,col+1))
return 1;
a[row][col] = 0;
return dfs(row,col+1);
}
void printans()
{
int i,j;
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
printf("%d ",a[i][j]);
puts("");
}
}
int main()
{
int i;
cin>>t;
while(t--)
{
cin>>n>>m;
for(i=0;i<n;i++)
cin>>r[i];
for(i=0;i<m;i++)
cin>>c[i];
if(dfs(0,0))
printans();
else
puts("没解玩个屁啊!!");
}
return 0;
}
正解
直接暴力搜索每个位置放了或者没放,可能的优化:
- 如果某一行/列超过了限制或者全选了也不够,直接咔嚓掉;
- 从最后一行/列开始搜索,因为权值大能够对后面有比较大的影响。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
int t;
int n,m;
int r[21],c[21],a[21][21];
int flag;
void print()
{
int i,j;
for(i=1;i<=m;i++)
if(c[i])
return;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
printsp(a[i][j]);
puts("");
}
return;
}
void dfs(int x,int y)
{
if(flag)
return;
if(y==0)
{
if(r[x])
return;
dfs(x-1,m);
return;
}
if(y*(y+1)/2<r[x])
return;
if(x==0)
{
print();
flag = 1;
return;
}
if(x*(x-1)/2>=c[y])
a[x][y] = 0,dfs(x,y-1);
if(r[x]>=y&&c[y]>=x)
{
if(x!=1||!(c[y]-x))
{
r[x] -= y,c[y] -= x,a[x][y] = 1;
dfs(x,y-1);
a[x][y] = 0,r[x] += y,c[y] += x;
}
}
return;
}
int main()
{
read(t);
while(t--)
{
read(n,m);
int i;
for(i=1;i<=n;i++)
read(r[i]);
for(i=1;i<=m;i++)
read(c[i]);
flag = 0;
dfs(n,m);
}
}
C 卡特兰
题意
小 \(A\) 对卡特兰数情有独钟, 所以他认为 \(1,2,5,14,42,...\) 这样的序列是好的。 换句话说, 只要满足 \(a_1=1,a_2=2,a_3=5,a_4=14,a_5=42\) 的序列都是好的。
现在给出一个长度为 6 的好的序列 \(a_1=1,a_2=2,a_3=5,a_4=14,a_5=42,a_6\). 你需要帮他构造一个有限状态自动机使得 \(\forall i=1,...,6\) 这个自动机正好能够接受 \(a_i\) 个长度为 \(i\) 的字符串。
一个有限状态自动机由 \((Q,\Sigma,\delta,q_0,F)\) 构成。
- \(Q\) 为状态集合, 在本题中你可以看成编号为 \(1,2,...,n\) 的节点, 你构造的自动机需要给出总状态数 \(n\).
- \(\Sigma\) 为合法字符集合, 你构造的自动机需要给出 \(|\Sigma|\)参数。
- \(\delta\) 表示状态转移, 可以看成一个 \(Q\times \Sigma\rightarrow Q\) 的映射, \(\delta(q,ch)=p\) 可以理解为在某个状态 \(q\) 接收了某个字符 \(ch\) 之后会走到状态 \(p\)。 如果 \(p=\emptyset\) 则表示在状态 \(q\) 不能接收字符 \(ch\).
- \(q_0\) 为开始状态,本题中我们默认 \(q_0=1\) 节点为开始状态。
- \(F\) 为终止状态集合,如果走过一个字符串之后停在了一个终止状态上, 称这个字符串被该自动机所接受。注意,如果在中途走到了终止状态之后仍然可以继续在自动机上走。
如果有多组合法的解你可以输出任意一种,但是你希望总状态数 \(n\) 尽可能小。
数据范围:\(10\) 个测试点,第 \(i\) 个测试点的 \(a_6=120+2i\)。
输入格式:
一行一个整数 \(a_6\)。
输出格式
第一行两个正整数 \(n,|\Sigma|\) 表示状态总数和字符集大小。
第二行 \(n\) 个数 \(f_i\in \{0,1\}\), \(f_i=1\) 表示 \(i\) 号状态属于终止状态。
接下来 \(n\) 行每行 \(|\Sigma|\) 个整数,第 \(i\) 行第 \(j\) 列表示状态 \(i\) 在接收到了第 \(j\) 个字符时的转移, \(0\) 表示 \(\emptyset\)。
样例输入:
131
样例输出:
3 20
1 0 0
1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
考场思路
什么答辩题,不会,读不懂。
无代码。
正解
本质上就是要构造一个有向图,可以把重边的数看成边权,同时上面的字母是不重要的,考虑怎么通过搜索构造这个有向图。
我们需要确定的东西:重点集合,边权。
整体所搜的想法就是一个一个地加上,搜索连边情况减掉不用的状态。
- 如果某个时刻能够接受的状态已经超过了,剪枝;
- 限制每条边的边权上界;
- 在估计某个时刻能够接受的状态的时候加上不能走到终点的点的贡献,因为这些点一定在之后的某个时刻能够走到终点。
- \(\cdots\)
最后通过限制我们发现所有点都能在 \(n\le 4\) 有解,在限制了边权上界为 \(4\) 能够很快地求解出答案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int x;
main(){
cin>>x;
cout<<"4 ";
if(x==122)cout<<"4\n1 0 0 0\n1 4 0 0\n1 4 4 4\n1 2 2 2\n1 3 4 0";
if(x==124)cout<<"5\n1 0 0 0\n1 4 0 0 0\n3 0 0 0 0\n1 2 2 4 4\n1 2 3 3 0";
if(x==126)cout<<"5\n1 0 0 0\n1 4 0 0 0\n3 0 0 0 0\n1 4 4 4 4\n1 2 4 4 0";
if(x==128)cout<<"6\n1 0 0 0\n1 4 0 0 0 0\n3 0 0 0 0 0\n2 4 0 0 0 0\n1 2 2 3 4 4";
if(x==130)cout<<"5\n1 0 0 0\n1 4 0 0 0\n3 0 0 0 0\n2 3 4 0 0\n1 2 3 4 4";
if(x==132)cout<<"4\n1 0 0 0\n1 4 0 0\n3 0 0 0\n2 3 3 4\n1 3 4 4";
if(x==134)cout<<"6\n1 0 0 0\n1 4 0 0 0 0\n3 0 0 0 0 0\n2 2 2 3 3 4\n1 3 4 4 0 0";
if(x==136)cout<<"7\n1 0 0 0\n1 4 0 0 0 0 0\n3 0 0 0 0 0 0\n1 2 2 3 3 3 3\n1 2 4 4 0 0 0";
if(x==138)cout<<"6\n1 0 0 0\n1 4 0 0 0 0\n3 4 0 0 0 0\n2 3 3 3 3 3\n1 2 3 3 4 4";
if(x==140)cout<<"7\n1 0 0 0\n1 4 0 0 0 0 0\n3 4 0 0 0 0 0\n2 3 3 3 3 3 3\n1 2 3 3 4 4 0";
return 0;
}
D 完全回文数
题意
给定 \(k\),输出第 \(k\) 小的既是完全平方数又是回文数的正整数。
数据范围:\(1\le k\le 485,1\le T\le 485\)。
输入格式:第一行一个正整数 \(T\), 表示测试数据组数。
每组测试数据一行一个正整数 \(k\)。
输出格式
对于每组测试数据,输出一行一个数, 表示第 \(k\) 小的既是完全平方数又是回文数的正整数。
样例输入:
7
1
2
3
4
5
6
100
样例输出:
1
4
9
121
484
676
1020322416142230201
考场思路
打表 yyds!
先把样例中连续的部分复制下来,不需要太长,长这样子:
1
4
9
121
484
把这个规律的所有答案存下来。
然后把第二项提取出来,转换成 string 格式,不怕炸 long long
,就得出了表。
把他扔到程序里就好了。
正解
同思路。
这是真的。
正解是打表。
。
不给代码了。
Day 10
\(100+70+0+0\) pts。Rank 24/58。
A 最短路径
题意
小 \(A\) 居住在风景优美的下北泽,城市里由 \(n\) 个路口和 \(m\) 条双向道路连通, 每个路口都有一个红绿灯, 绿灯表示可以选择一个道路通行,红灯表示需要在当前的路口等待。初始所有路口都是绿灯, \(k\) 秒后所有路口会变成红灯, 再 \(k\) 秒后所有路口又会变回绿灯, 如此往复。
如果在刚到某个路口的时候正好灯的颜色正在转换,那么以转换之后的颜色为准。
现在小 \(A\) 想要从 \(1\) 号路口走到 \(n\) 号路口, 求他最少需要的时间,保证至少存在一条从 \(1\) 号路口到 \(n\) 号路口的路径。
数据范围:\(1\le n\le 10^4,1\le m\le 10^5,1\le k\le 10^2,1\le w\le 10^3\)。
输入格式
第一行三个正整数 \(n,k,m\).
接下来 \(m\) 行每行三个整数 \(u,v,w\) 表示存在一条连通 \(u\) 到 \(v\), 需要 \(w\) 秒通过时间的双向道路。
输出格式
输出一行一个整数表示最少需要的时间。
样例输入:
7 4 7
1 2 3
2 3 1
1 4 4
4 6 7
7 5 2
3 5 1
4 5 5
样例输出:
11
考场思路
注意到我们没有必要再绿灯的时候等,所以直接 dijsktra,然后每次计算一下下一次绿灯时什么时候即可。
复杂度 \(O(m\log n)\)。
代码(\(100\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
int n,k,m;
int dis[100001];
struct edge
{
int to,w;
edge(int t, int we) : to(t), w(we) {}
};
vector<edge> g[10001];
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
int dij(int s)
{
memset(dis,0x3f,sizeof(dis));
dis[s] = 0;
pq.push(make_pair(0,s));
while(!pq.empty())
{
int u = pq.top().second;
int time = pq.top().first;
// printf("%d %d\n",u,time);
pq.pop();
if(u==n)
return time;
int nxt_color = (time/k)%2;
// printf("%d\n",nxt_color);
//0 green;1 red
for(const edge& i:g[u])
{
int v = i.to;
int w = i.w;
// printf("%d %d\n",v,w);
int wait = nxt_color==1?(k-(time%k)):0;
if(time+wait+w<dis[v])
{
dis[v] = time+wait+w;
pq.push(make_pair(dis[v],v));
}
}
}
return -1;
}
int main()
{
read(n,k,m);
int i;
for(i=1;i<=m;i++)
{
int u,v,w;
read(u,v,w);
g[u].emplace_back(v,w);
g[v].emplace_back(u,w);
}
cout<<dij(1)<<endl;
return 0;
}
正解
同思路。
B 跳棋盘
题意
有一个 \(n\times n\) 的棋盘,棋盘的每个格子上有一个在 \(1-k\) 内的数字。
现在你想要从棋盘上的某个 \(1\) 开始,按照顺序从 \(1\) 跳到 \(k\)。当你从 \((x_1,y_1)\) 跳到 \((x_2,y_2)\) 时会使用 \(|x_1-x_2|+|y_1-y_2|\) 的体力。
你希望使用的体力最少,输出这个值。
输入格式
第一行一个正整数 \(T\), 表示测试数据组数。
每组测试数据第一行两个正整数 \(n,k\).
接下来 \(n\) 行每行 \(n\) 个正整数 \(a_{i,j}\) 表示棋盘上的数字。
输出格式
对于每组测试数据,输出一行一个整数,表示最少需要的体力,如果无解输出 \(-1\).
样例输入:
2
10 5
5 1 3 4 2 4 2 1 2 1
4 5 3 4 1 5 3 1 1 4
4 2 4 1 5 4 5 2 4 1
5 2 1 5 5 3 5 2 3 2
5 5 2 3 2 3 1 5 5 5
3 4 2 4 2 2 4 4 2 3
1 5 1 1 2 5 4 1 5 3
2 2 4 1 2 5 1 4 3 5
5 3 2 1 4 3 5 2 3 1
3 4 2 5 2 5 3 4 4 2
3 3
1 1 1
2 2 2
1 1 1
样例输出:
5
-1
考场思路
枚举哪个点是 \(1\),然后暴力 dfs,每次查找到 \(a_{i,j}+1\) 的数就递归。
得了 \(30\)。
最后一小时想到了优化,就是建图优化,然后暴力就行了。
代码(\(70\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace fastIO
{
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();T fl = 1;
while(ch<'0'||ch>'9'){if(ch=='-')fl = -1;ch = getchar();};
while(ch>='0'&&ch<='9'){x = x*10+ch-'0';ch = getchar();};
x = x*fl;
}
template<typename T, typename ...T1> void read(T &x, T1 &...x1)
{
read(x);
read(x1...);
}
template<typename T> void print(T x)
{
if(x<0)
{
x = -x;
putchar('-');
}
if(x/10)
print(x/10);
putchar(x%10+'0');
}
template<typename T> void printsp(T x)
{
print(x);
putchar(' ');
}
template<typename T, typename ...T1> void printsp(T &x, T1 &...x1)
{
printsp(x);
printsp(x1...);
}
template<typename T> void printen(T x)
{
print(x);
putchar('\n');
}
template<typename T, typename ...T1> void printen(T &x, T1 &...x1) //I love OoXiao_QioO very much!!!
{
printen(x);
printen(x1...);
}
}
using namespace fastIO;
int t,n,k;
int a[501][501];
vector<pair<int,int> > g[501*501];
map<int,int> m;
int flag;
ll dis[501][501];
int dist(int x,int y,int xx,int yy)
{
return abs(x-xx)+abs(y-yy);
}
int main()
{
// freopen("sample_22.in","r",stdin);
read(t);
while(t--)
{
read(n,k);
flag = 1;
int i,j;
for(i=1;i<=k;i++)
g[i].clear();
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
read(a[i][j]);
m[a[i][j]] = 1;
dis[i][j] = 0;
g[a[i][j]].emplace_back(make_pair(i,j));
}
}
for(i=1;i<=k;i++)
{
if(!m[i])
{
flag = 0;
puts("-1");
break;
}
}
if(!flag)
continue;
for(i=0;i<g[1].size();i++)
dis[g[1][i].first][g[1][i].second] = 0;
for(i=2;i<=k;i++)
{
for(j=0;j<g[i].size();j++)
{
int kk;
ll ans = 1e18;
for(kk=0;kk<g[i-1].size();kk++)
ans = min(ans,dist(g[i][j].first,g[i][j].second,g[i-1][kk].first,g[i-1][kk].second)+dis[g[i-1][kk].first][g[i-1][kk].second]);
dis[g[i][j].first][g[i][j].second] = ans;
}
}
ll ans = 1e18;
for(i=0;i<g[k].size();i++)
ans = min(ans,dis[g[k][i].first][g[k][i].second]);
if(ans==1e18)
ans = -1;
cout<<ans<<endl;
}
return 0;
}
正解
首先不难想到直接最短路计算,记 \(f(i,j)\) 表示跳到 \((i,j)\) 最少用的体力,那么转移就是枚举上一个位置然后加上曼哈顿距离求最小值。
考虑优化,我们注意到如果转移都在左上的话坐标正负的贡献是固定的。所以可以使用数据结构维护。先按照一维扫描线,另一维可以使用线段树或者树状数组维护前缀/后缀最小值。
对四个象限分别计算一次即可。时间复杂度 \(O(n^2 \log n)\)。
代码:
点击查看代码
#include<bits/stdc++.h>
#define hh puts("");
using namespace std;
namespace fastio{const int bufl=1<<20;const double base1[16]={1,1e-1,1e-2,1e-3,1e-4,1e-5,1e-6,1e-7,1e-8,1e-9,1e-10,1e-11,1e-12,1e-13,1e-14,1e-15};const double base2[16]={1,1e1,1e2,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13,1e14,1e15};struct IN{FILE*IT;char ibuf[bufl],*is=ibuf,*it=ibuf;IN(){IT=stdin;}IN(char*a){IT=fopen(a,"r");}inline char getChar(){if(is==it){it=(is=ibuf)+fread(ibuf,1,bufl,IT);if(is==it)return EOF;}return*is++;}template<typename Temp>inline void getInt(Temp&a){a=0;int b=0,c=getChar();while(c<48||c>57)b^=(c==45),c=getChar();while(c>=48&&c<=57)a=(a<<1)+(a<<3)+c-48,c=getChar();if(b)a=-a;}template<typename Temp>inline void getDouble(Temp&a){a=0;int b=0,c=getChar(),d=0;__int128 e=0,f=0;while(c<48||c>57)b^=(c==45),c=getChar();while(c>=48&&c<=57)e=(e<<1)+(e<<3)+c-48,c=getChar();if(c==46){c=getChar();while(c>=48&&c<=57)d++,f=(f<<1)+(f<<3)+c-48,c=getChar();}a=e+base1[d]*f;if(b)a=-a;}IN&operator>>(char&a){a=getChar();return*this;}IN&operator>>(char*a){do{*a=getChar();}while(*a<=32);while(*a>32)*++a=getChar();*a=0;return*this;}IN&operator>>(string&a){char b=getChar();while(b<=32)b=getChar();while(b>32)a+=b,b=getChar();return*this;}IN&operator>>(int&a){getInt(a);return*this;}IN&operator>>(long long&a){getInt(a);return*this;}IN&operator>>(__int128&a){getInt(a);return*this;}IN&operator>>(float&a){getDouble(a);return*this;}IN&operator>>(double&a){getDouble(a);return*this;}IN&operator>>(long double&a){getDouble(a);return*this;}};struct OUT{FILE*IT;char obuf[bufl],*os=obuf,*ot=obuf+bufl;int Eps;long double Acc;OUT(){IT=stdout,Eps=6,Acc=1e-6;}OUT(char*a){IT=fopen(a,"w"),Eps=6,Acc=1e-6;}inline void ChangEps(int x=6){Eps=x;}inline void flush(){fwrite(obuf,1,os-obuf,IT);os=obuf;}inline void putChar(int a){*os++=a;if(os==ot)flush();}template<typename Temp>inline void putInt(Temp a){if(a<0){putChar(45);a=-a;}if(a<10){putChar(a+48);return;}putInt(a/10);putChar(a%10+48);}template<typename Temp>inline void putDouble(Temp a){if(a<0){putChar(45);a=-a;}__int128 b=a;putInt(b);a-=b;a*=base2[Eps];b=a+Acc;putChar(46);putInt(b);}OUT&operator<<(char a){putChar(a);return*this;}OUT&operator<<(char*a){while(*a>32)putChar(*a++);return*this;}OUT&operator<<(string a){for(auto c:a)putChar(c);return*this;}OUT&operator<<(int a){putInt(a);return*this;}OUT&operator<<(long long a){putInt(a);return*this;}OUT&operator<<(__int128 a){putInt(a);return*this;}OUT&operator<<(float a){putDouble(a);return*this;}OUT&operator<<(double a){putDouble(a);return*this;}OUT&operator<<(long double a){putDouble(a);return*this;}~OUT(){flush();}};}using fastio::IN;using fastio::OUT;IN fin;OUT fout;
#define endl '\n'
#define cin fin
#define cout fout
const int N = 501;
int n,a[N][N],k;
int dis[N][N];
vector<pair<int,int> > pos[N*N+1];
struct Tree
{
#define ls p<<1
#define rs p<<1|1
int val[N*4+1];
void init()
{
int i;
for(i=0;i<N*4;i++)
val[i] = 1e9;
}
inline void modify(int p,int l,int r,int pos,int value)
{
if(l==r)
{
val[p] = min(val[p],value);
return;
}
int mid = (l+r)>>1;
if(pos<=mid)
{
modify(ls,l,mid,pos,value);
val[p] = min(val[p],val[ls]);
}
else
{
modify(rs,mid+1,r,pos,value);
val[p] = min(val[p],val[rs]);
}
}
inline int query(int p,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
return val[p];
int mid = (l+r)>>1;
int ans = 1e9;
if(L<=mid)
ans = min(ans,query(ls,l,mid,L,R));
if(R>mid)
ans = min(ans,query(rs,mid+1,r,L,R));
return ans;
}
inline void MODIFY(int pos,int value)
{
modify(1,1,n,pos,value);
}
inline int QUERY(int L,int R)
{
return query(1,1,n,L,R);
}
}Tree1,Tree2;
inline bool cmp1(pair<int,pair<int,int> > a,pair<int,pair<int,int> > b)
{
if(a.second.first!=b.second.first)
return a.second.first<b.second.first;
if(a.first!=b.first)
return a.first<b.first;
return a.second.second<b.second.second;
}
inline bool cmp2(pair<int,pair<int,int> > a,pair<int,pair<int,int> > b)
{
if(a.second.first!=b.second.first)
return a.second.first>b.second.first;
if(a.first!=b.first)
return a.first<b.first;
return a.second.second<b.second.second;
}
inline void Work(vector<pair<int,pair<int,int> > > E)
{
sort(E.begin(),E.end(),cmp1);
Tree1.init();
Tree2.init();
int i;
for(i=0;i<E.size();i++)
{
if(E[i].first==0)
{
int d = dis[E[i].second.first][E[i].second.second];
Tree1.MODIFY(E[i].second.second,d-E[i].second.first-E[i].second.second);
Tree2.MODIFY(E[i].second.second,d-E[i].second.first+E[i].second.second);
}
else
{
int &d = dis[E[i].second.first][E[i].second.second];
d = min(d,Tree1.QUERY(1,E[i].second.second)+E[i].second.first+E[i].second.second);
d = min(d,Tree2.QUERY(E[i].second.second,n)+E[i].second.first-E[i].second.second);
}
}
sort(E.begin(),E.end(),cmp2);
Tree1.init();
Tree2.init();
for(i=0;i<E.size();i++)
{
if(E[i].first==0)
{
int d = dis[E[i].second.first][E[i].second.second];
Tree1.MODIFY(E[i].second.second,d+E[i].second.first-E[i].second.second);
Tree2.MODIFY(E[i].second.second,d+E[i].second.first+E[i].second.second);
}
else
{
int &d = dis[E[i].second.first][E[i].second.second];
d = min(d,Tree1.QUERY(1,E[i].second.second)-E[i].second.first+E[i].second.second);
d = min(d,Tree2.QUERY(E[i].second.second,n)-E[i].second.first-E[i].second.second);
}
}
}
inline void solve()
{
int i,j;
for(i=0;i<N*N;i++)
pos[i].clear();
cin>>n>>k;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
cin>>a[i][j];
pos[a[i][j]].push_back(make_pair(i,j));
dis[i][j] = 1e9;
}
}
for(i=1;i<=k;i++)
{
if(pos[i].empty())
{
cout<<-1<<endl;
return;
}
}
for(auto v:pos[1])
dis[v.first][v.second] = 0;
for(i=2;i<=k;i++)
{
vector<pair<int,pair<int,int> > > E;
for(auto v:pos[i-1])
E.push_back(make_pair(0,v));
for(auto v:pos[i])
E.push_back(make_pair(1,v));
Work(E);
}
int ans = 1e9;
for(auto v:pos[k])
ans = min(ans,dis[v.first][v.second]);
cout<<ans<<endl;
return;
}
signed main()
{
int T = 1;
cin>>T;
while(T--)
solve();
return 0;
}
C 树
题意
小 \(A\) 有一棵 \(n\) 个点的树,树上的每个节点都有一个权值 \(v_i\), 保证 \(v_i\) 只有最多三个质因数。
现在他会给你 \(Q\) 次询问,每次询问 \((u,v)\). 你需要告诉他在 \(u\) 到 \(v\) 的路径上有多少种方式选择两个权值互质的点。
数据范围:\(1\ne n,q\le 2.5\times 10^4,1\le v_i\le 10^7\)。
输入格式
第一行两个正整数 \(n,Q\).
第二行 \(n\) 个正整数 \(v_1,v_2,\cdots,v_n\).
接下来 \(n-1\) 行每行两个正整数 \(a_i,b_i\) 表示树上的一条边。
接下来 \(Q\) 行每行两个正整数 \(u,v\) 表示一次询问。
输出格式
输出 \(Q\) 行每行一个整数表示方案数。
样例输入:
6 5
3 2 4 1 6 5
1 2
1 3
2 4
2 5
3 6
4 6
5 6
1 1
1 6
6 1
样例输出:
9
6
0
3
3
考场思路
答辩题,不会。
正解
首先每个数只有质因子有用,所以我们只需要保留每个数质因子的乘积即可。
那么通过简单的容斥或莫比乌斯反演我们可以将我们要求的 \(\sum_{i,j}[gcd(v_i,v_j)=1]\) 转化为 \(\sum_{g}\mu(g) \sum_{i,j} [g|v_i,g|v_j]\)
那么考虑假设我们要在路径中加入一个点或者删除一个点, 我们最多只需要枚举8个不同的\(g\) 并计算当前路径中有多少数是 \(g\) 的倍数,也就是说插入和删除的维护和更新可以在 \(O(1)\) 完成。
假设树的形态是一条链且我们可以快速的进行插入和删除,那么我们直接使用序列莫队的技巧即可。
对于一般的情况我们可以使用树上莫队的技巧,首先对树跑一边dfs, 在进入每个点(开始位置)和走出每个点(结束位置)的时刻同时记录, 这样就得到了一个长度为 \(2n\) 的序列,序列上的相邻元素在树上也相邻。注意到如果要统计树上的一条路径 \((u,v)\) 那么我们只需要将 \(u\) 的结束位置到 \(v\) 的开始位置对应序列的区间取出, 忽略出现两次的元素, 剩余的出现正好一次的元素再加上lca就是路径上的点集。 注意如果 \(u\) 是 \(v\) 的祖先需要特判。
于是就将树上莫队转化成了序列莫队问题求解。
最终时间复杂度 \(O(m\sqrt n)\)。
太难了代码敲不动了/ll/ll
D 树树树
题意
小 \(A\) 有一棵 \(n\) 个点的无向树,每条边长度都是 \(1\), 他选择了树上的三个不同的顶点 \(u_1,u_2,u_3\) 作为秘密据点,并对每个点 \(v\) 计算了 \(M_v=median(d(u_1,v),d(u_2,v),d(u_3,v))\),\(d\) 表示两个点的距离, median 是中位数。
现在小 \(B\) 截取了所有的 \(M_v\), 他想要知道在此条件下小 \(A\) 可能的 \(u_1,u_2,u_3\) 有多少种(不考虑顺序)。
数据范围:\(2\le n\le 10^5,1\le T\le 5,1\le V_i \le i,1\le M_i \le n-1\)。
输入格式
第一行一个正整数 \(T\) 表示测试数据。
每组测试数据第一行一个正整数 \(n\)。
第二行 \(n-1\) 个整数 \(V_1,\cdots, V_{n-1}\) 表示存在一条 \((V_i,i+1)\) 的无向边。
第三行 \(n\) 个整数 \(M_1,M_2,\cdots,M_n\)。
输出格式
对于每组测试数据输出一行一个整数表示答案。
样例输入:
2
8
1 2 1 4 1 6 4
2 1 2 1 2 3 4 2
5
1 2 3 4
1 1 1 1 1
样例输出:
2
0
考场思路
什么答辩题,不会。
正解
首先我们注意到任意两个秘密据点的路径中点是重要的,为了让这个中点不出现在边上一个常见的技巧就是在每条边上新建一个点。
然后我们先考虑在扩建了树之后每个 \(M\) 值的改变,原先树上的点 \(M\) 值会变成两倍。 考虑原图中的边 \((u,v)\) 中间新建的点 \(x\) 那么首先必须有 \(|M_u-M_v|\le 2\) 如果 \(|M_u-M_v|=2\) 那么 \(M_x=(M_u+M_v)/2\). 如果 \(M_u=M_v\) 这时候这条边中点 \(x\) 一定是某两个秘密据点的中点,也就是说这种边最多只有3个。这些 \(M_x=M_u \pm 1\), 可以直接暴力枚举。
接着我们考虑 \(u_1,u_2,u_3\) 的位置关系,它们三个点一定有一个中心 \(u\), 不妨记 \(D_i=d(u,u_i)\), 并且假设 \(D_1\le D_2\le D_3\).
我们按照 \(M\) 值从大到小给边定向, 如果一个点没有出度则称为汇点。 通过观察: 当 \(D_1<D_2\le D_3\) 的时候有且只有两个汇点。 当 \(D_1=D_2\le D_3\) 的时候有且只有一个汇点。计算出汇点的个数分类讨论。
一个汇点的情况: 此时汇点一定是三个点的中心, 设汇点为 \(r\), 且 \(D_1=D_2=M_r,D_3>M_r\) 我们只需要通过简单的背包的\(dp\) 计数即可。
两个汇点的情况: 设两个汇点为 \(r_1,r_2\). 我们会发现 \(u_1,u_2,u_3\) 的点到 \(r_1,r_2\) 的距离已经全部确定了,只要分别独立的满足这些距离, 将方案数相乘即可,两次 dfs 即可。
最终时间复杂度 \(O(n)\)。
太难了代码敲不动了/ll/ll
Day 11
\(70+50+8+12=140\) pts。Rank 28/58。
A 子序列
题意
小 A 有 \(n\) 个球,每个球有两个属性 \(a_i,b_i\)。
小 A 想从他的这些球中选出一些来,假设选的球的编号为 \(p_1,p_2,\dots,p_k\),小 A 想要最大化 \(\sum\limits_{i=1}^{k} b_{p_i}-(\max\limits_{i=1}^k a_{p_i}-\min\limits_{i=1}^k a_{p_i})\)。
小 A 想知道她想要最大化的值最大能有多少。
数据范围:\(1\le n\le 5\times 10^5,1\le a_i \le 10^{15},1\le b_i \le 10^9\)。
输入格式
第一行一个整数 \(n\)。
接下来 \(n\) 行每行两个整数,第 \(i+1\) 行表示 \(a_i,b_i\)。
输出格式
输出一行一个整数,表示答案。
样例输入:
4
7 4
1 3
10 3
2 68
考场思路
前 \(20\min\) 快速做出前 \(70\) 分的 DP,然后优化了好久。
f'f'ff'f'f'f
发现是个诈骗题,直接考虑按照 \(a\) 排序,选择的必然是个区间。
令 \(preb_i\) 表示 \(b\) 的前缀和数组,那么一个区间 \([l,r]\) 的价值为,\(preb_r-preb_{l-1}-(a_r-a_l))\)。
枚举 \(r\),记录最小的 \(preb_{l-1}-a_l\) 即可。
时间复杂度 \(O(n)\)。
考场思路正确,但是 \(5\times10^5\) 的数组开成了 \(5\times10^4\)。。。。。
警钟撅烂磨成粉!!
代码(\(70\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N = 50005;
struct node
{
ll a,b;
}a[N];
ll qzh[N],ma[N];
int n;
bool cmp(node a,node b)
{
return a.a<b.a;
}
void solve()
{
cin>>n;
int i;
for(i=1;i<=n;i++)
cin>>a[i].a>>a[i].b;
sort(a+1,a+1+n,cmp);
for(i=1;i<=n;i++)
qzh[i] = qzh[i-1]+a[i].b;
for(i=1;i<=n;i++)
ma[i] = max(ma[i-1],a[i].a-qzh[i-1]);
ll maa = INT_MIN;
for(i=1;i<=n;i++)
maa = max(maa,qzh[i]-a[i].a+ma[i]);
cout<<maa<<endl;
return;
}
signed main()
{
// freopen("seq2.in","r",stdin);
int T = 1;
// cin>>T;
while(T--)
solve();
return 0;
}
正解
把数组开大即可通过。
B 排列
题意
对于一个长度为 \(n\) 的排列 \(p\),我们认为一个位置 \(i\ (2\le i\le n-1)\) 是“顶”,当且仅当 \(p_{i-1}<p_i\) 且 \(p_i>p_{i+1}\)。
小 B 想知道所有长度为 \(n\) 的排列,有多少个恰好有 \(k\) 个位置是“顶”。
答案对 \(10^9+7\) 取模。
数据范围:\(3\le n\le 5000,1\le k\le n\)。
输入格式
第一行两个整数 \(n,k\)。
输出格式
一行一个整数,表示答案。
样例输入:
3 1
样例输出:
2
考场思路
打了个全排列暴力,然后把 \(n=10\) 的所有 \(k\) 打出来,扔到 oeis.org 上,然后找到表了,直接提交,可惜长度不够,\(50\) pts。
不放代码了。
正解
令 \(dp_{i,j}\) 表示长度为 \(i\) 有 \(j\) 个顶的排列个数。
考虑每次往长度为 \(i\) 的排列里插入 \(i+1\),那么如果插入在原本的顶的旁边,这个时候顶的个数就是不变的,否则顶的个数会增加 \(1\),即:
\(dp_{i,j}=dp{i-1,j}\times(2\times j+2)+dp{i-1,j-1}\times (i-(2\times j))\)
时间复杂度 \(O(n^2)\)。
代码:
点击查看代码
#define int long long
#define mod 1000000007
int n,k,dp[5005][5005];
signed main(){
std::cin>>n>>k;
dp[2][0]=2;
for(int i=3;i<=n;i++)for(int j=0;j<=std::min((i-1)/2,k);j++){
dp[i][j]=(dp[i][j]+dp[i-1][j]*(2*j+2))%mod;
if(j)dp[i][j]=(dp[i][j]+dp[i-1][j-1]*(i-2*j))%mod;
}
std::cout<<dp[n][k];
}
C 闪电
题意
二维平面上有 \(n\) 个点。
小 C 对折线很感兴趣,所以他想求出有多少个这 \(n\) 个点的非空子集能画出“闪电”。
形式化地,如果一个点集 \(\{(x_1, y_1), \dots ,(x_k, y_k)\}\) 能画出“闪电”,当且仅当把它们按照 \(y_i\) 降序排序后满足:
- \(\forall j\in [3,k], x_{j-2}<x_j<x_{j-1} \ \text{or}\ x_{j-1}<x_j<x_{j-2}\)
你只需要告诉他答案对 \(10^9 + 7\) 取模后的结果。
数据范围:\(1\le n\le 6000,0\le |x_i|,|y_i|\le 10^9,v_i \ne j,x_i \ne x_j,y_i \ne y_j\)。
输入格式
第一行一个整数 \(n\),表示点的个数
接下来 \(n\) 行,每行两个整数 \(x_i, y_i\),表示第 \(i\) 个点的坐标。
输出格式
一行一个整数表示答案。
样例输入:
4
2 2
3 1
1 4
4 3
样例输出:
14
考场思路
全排列枚举。
不放代码了
正解
按照 \(x_i\) 排序。
设 \(dp_{i,0/1}\) 表示以第 \(i\) 个点为顶端接下来向左/向右的折线方案数。
考虑依次加入 \((x_1,y_1),(x_2,y_2),\cdots,(x<n,y_n)\) 然后更新 DP 数组。
由于新点横坐标最大,所以只可能在折线的第一位或第二位。
转移的时候可以倒序枚举 \(j\):
- \(y_i>y_j:dp_{i,0}\gets dp_{i,0}+dp_{j,1}\)
- \(y_i<y_j:dp_{j,1}\gets dp_{j,1}+dp_{i,0}\)
这样就能够做到时间复杂度 \(O(n^2)\),空间复杂度 \(O(n)\) 了。
点击查看代码
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
struct Node{int x,y;}A[6005];
bool cmp(Node a,Node b){return a.x<b.x;}
int l[6005],r[6005],n,ans;
main(){
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld%lld",&A[i].x,&A[i].y);
sort(A+1,A+n+1,cmp);
for(int i=1;i<=n;i++)r[i]=l[i]=1;
for(int i=1;i<=n;i++)for(int j=i-1;j>=1;j--){
if(A[i].y<A[j].y)r[j]=(r[j]+l[i])%mod;
else l[i]=(l[i]+r[j])%mod;
}
for(int i=1;i<=n;i++)ans=(ans+l[i]+r[i])%mod;
cout<<(ans-n+mod)%mod;
}
D 三
题意
给出一个长度为 \(n\) 的序列 \(a\),有 \(q\) 次询问。
每次询问会给出两个整数 \(l, r\),你需要求 \(\max\limits_{l\le i<j<k\le r,j−i\le k−j}(a_i + a_j + a_k)\)。
数据范围:\(3\le n\le 5\times 10^5,1\le q\le 5\times 10^5,1\le a_i\le 10^9,1\le i<l+2\le r\le n\)。
输入格式
第一行一个整数 \(n\)。
第二行 \(n\) 个整数表示序列 \(a\)。
第三行一个整数 \(q\)。
接下来 \(q\) 行每行两个整数 \(l, r\)。
输出格式
输出 \(q\) 行,第 \(i\) 行表示第 \(i\) 个询问的答案。
样例输入:
5
5 2 1 5 3
3
1 4
2 5
1 5
样例输出:
12
9
12
考场思路
三重循环大暴力,不放代码了。
正解
如有 \(a_i<a_j(i<j)\),存在 \(k\) 满足 \(1<k<j,a_k>a_i\),显然 \(a_k,a_j\) 作为前两个更优。
同理,如有 \(a_i>a_j(i<j>)\) 存在 \(k\) 满足 \(1<k<j,a_k>a_j\),显然 \(a_i,a_k\) 作为前两个更优。
那么有用的前两个的对就只剩下了 \(O(n)\) 个,具体为每个数和它左边的第一个比他的大的数形成的对,以及每个数和它右边的第一个大于等于它的数形成的对。
接着,对于询问,按照左端点从大到小排序,在求解的同时加入有用的边。
现在问题变成了,有 \(n\) 个标记 \(c_{1,\dots,n}\),一开始全为 \(0\),每次操作对 \(c\) 的后缀取 \(\max\),或者询问一个 \(c_j+a_i\) 的最大值。
线段树维护即可。r
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define fr first
#define sc second
inline int rd(){
int res=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
res=res*10+(ch-'0');
ch=getchar();
}
return res*f;
}
const int N=5e5+10;
int n;
int a[N],L[N],R[N];
struct node{
int l,r,d,tg,v,ans;
}t[N<<2];
#define ls p*2
#define rs p*2+1
#define mid (t[p].l+t[p].r)/2
void build(int p,int l,int r){
t[p].l=l;t[p].r=r;
if(l==r){
t[p].ans=t[p].v=a[l];
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
t[p].v=max(t[ls].v,t[rs].v);
t[p].ans=max(t[ls].ans,t[rs].ans);
}
inline void change(int p,int v){
if(v>t[p].d){
t[p].tg=t[p].d=v;
t[p].ans=max(t[p].ans,t[p].v+t[p].d);
}
}
inline void pd(int p){
if(!t[p].tg)return ;
change(ls,t[p].tg);
change(rs,t[p].tg);
t[p].tg=0;
}
void upd(int p,int l,int r,int v){
if(t[p].l==l&&t[p].r==r){
change(p,v);
return ;
}
pd(p);
if(l>mid)upd(rs,l,r,v);
else if(r<=mid)upd(ls,l,r,v);
else{
upd(ls,l,mid,v);
upd(rs,mid+1,r,v);
}
t[p].ans=max(t[ls].ans,t[rs].ans);
}
inline int query(int p,int l,int r){
if(t[p].l==l&&t[p].r==r){
return t[p].ans;
}
pd(p);
if(l>mid)return query(rs,l,r);
else if(r<=mid)return query(ls,l,r);
else{
return max(query(ls,l,mid),query(rs,mid+1,r));
}
}
#undef mid
int stk[N],tp,ans[N],cnt;
pii c[N*2];
vector `<pii>` vec[N];
signed main(){
n=rd();
for(int i=1;i<=n;i++){
a[i]=rd();
while(tp&&a[stk[tp]]<=a[i]){
R[stk[tp]]=i;
tp--;
}
stk[++tp]=i;
}
tp=0;
for(int i=n;i>=1;i--){
while(tp&&a[stk[tp]]<=a[i]){
L[stk[tp]]=i;
tp--;
}
stk[++tp]=i;
}
for(int i=1;i<=n;i++){
if(L[i])
c[++cnt]={L[i],i};
if(R[i])
c[++cnt]={i,R[i]};
}
sort(c+1,c+1+cnt);
// for(int i=1;i<=cnt;i++){
// cerr<<c[i].fr<<' '<<c[i].sc<<endl;
// }
build(1,1,n);
int q=rd();
for(int i=1;i<=q;i++){
int l=rd(),r=rd();
vec[l].push_back({r,i});
}
int pos=cnt;
for(int i=n;i>=1;i--){
while(c[pos].fr>=i&&pos>0){
int val=a[c[pos].fr]+a[c[pos].sc];
int st=2*c[pos].sc-c[pos].fr;
if(st<=n)upd(1,st,n,val);
pos--;
}
//cerr<<query(1,2,5)<<endl;
for(pii j:vec[i]){
ans[j.sc]=query(1,i,j.fr);
}
}
for(int i=1;i<=q;i++){
printf("%lld\n",ans[i]);
}
return 0;
}
</details>