A. 【线上训练13】二叉树
题解:贪心,先求出这两个序列,i在第一个序列位置为\(a[i]\),第二个为\(b[i]\),如果\(a[i] > b[i]\), \(ans += 2^{a[i]-b[i]}\)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int mo = 998244353;
int n,tot,cnt;
int l[maxn],r[maxn],p[maxn],q[maxn],tmp[maxn];
int vis[maxn],ans1[maxn],ans2[maxn];
ll ans;
void dfs1(int x){
p[++cnt] = x;
if(l[x])
dfs1(l[x]);
if(r[x])
dfs1(r[x]);
}
void dfs2(int x){
if(l[x])
dfs2(l[x]);
if(r[x])
dfs2(r[x]);
q[++tot] = x;
}
ll qp(ll a,ll b){
ll ans = 1,base = a;
while(b != 0){
if(b & 1)ans = ans * base % mo;
base = base * base % mo;
b >>= 1;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++){
scanf("%d%d",&l[i],&r[i]);
}
dfs1(1);
dfs2(1);
for(int i = 1;i <= n;i ++){
if(vis[p[i]] == 0 && vis[q[i]] == 0){
vis[p[i]] = vis[q[i]] = 1;
if(p[i] != q[i])tmp[p[i]] = 1,tmp[q[i]] = 0;
else tmp[p[i]] = tmp[q[i]] = 0;
ans1[i] = 1;
ans2[i] = 0;
}
if(vis[p[i]] && vis[q[i]]){
ans1[i] = tmp[p[i]];
ans2[i] = tmp[q[i]];
}
if(vis[p[i]] && vis[q[i]] == 0){
ans1[i] = tmp[p[i]];
ans2[i] = 0;
tmp[q[i]] = 0;vis[q[i]] = 1;
}
if(vis[p[i]] == 0 && vis[q[i]]){
ans2[i] = tmp[q[i]];
ans1[i] = 1;
tmp[p[i]] = 1;vis[p[i]] = 1;
}
}
ll sum1 = 0,sum2 = 0;
for(int i = 1;i <= n;i ++){
if(ans1[i])sum1 = (sum1 + ans1[i] * qp(2,n - i)) %mo;
if(ans2[i])sum2 = (sum2 + ans2[i] * qp(2,n - i)) %mo;
}
ans = (sum1 - sum2 + mo)%mo;
cout<<ans<<endl;
return 0;
}
B. 【线上训练13】子序列
题解:子序列\(dp\),\(dp[i]\)表示到i的子序列个数,转移见代码。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 5010100;
const int mo = 998244353;
int m,len;
char c[maxn],s[maxn<<1],tmp[maxn];
int last[30];
ll dp[maxn];
ll ans;
int main()
{
scanf("%d",&m);
scanf("%s",c + 1);
len = 0;
for(int i = 1;i <= m;i ++){
len *= 2;
for(int j = 1;j <= len;j ++){
if(j % 2 == 1)tmp[j] = c[i];
else tmp[j] = s[j/2];
}
tmp[++len] = c[i];
for(int j = 1;j <= len;j ++)s[j] = tmp[j];
}
for(int i = 1;i <= len;i ++){
if(last[s[i] - 'a'] == 0)dp[i] = (dp[i-1]*2% mo + 1)% mo;
else dp[i] = (dp[i - 1] * 2 % mo - dp[last[s[i] - 'a'] - 1] + mo) %mo;
last[s[i] - 'a'] = i;
// cout<<dp[i]<<endl;
}
cout<<dp[len]<<endl;
return 0;
}
T1.answer
暴力map哈希,记得特判\(p = 0,q = 0\)的情况。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
string s[100101];
int flag[1010];
int n,m,p,q;
map <string,int> mp;
void dfs(int x){
if(x == m + 1){
string tmp,tmp1;
tmp.clear();
for(int i = 1;i <= m ;i ++)
if(flag[i] == 1)tmp += 'Y';else tmp += 'N';
for(int i = 1;i <= m ;i ++)
if(flag[i] == 1)tmp1 += 'N';else tmp1 += 'Y';
if(mp[tmp] == 0 && mp[tmp1] == 0){
cout<<tmp<<endl;exit(0);
}
return ;
}
flag[x] = 0;
dfs(x + 1);
flag[x] = 1;
dfs(x + 1);
}
int main()
{
//freopen("answer.in","r",stdin);
//freopen("answer.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i = 1;i <= n ;i ++){
cin >> s[i];
mp[s[i]] ++;
}
sort(s + 1,s + 1 + n);
if(p == 0 && q == 0){
dfs(1);
}
if(p == 0 && q){
for(int i = n ;i >= 1; i--){
string tmp;
for(int j = 0;j < m;j ++)
if(s[i][j] == 'N')tmp += 'Y';else tmp += 'N';
if(mp[s[i]] == q){
cout<<tmp<<endl;
return 0;
}
}
}
for(int i = 1;i <= n ;i ++){
string tmp;
for(int j = 0;j < m;j ++)
if(s[i][j] == 'N')tmp += 'Y';else tmp += 'N';
if(mp[s[i]] == p && mp[tmp] == q){
cout<<s[i]<<endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}
T2.seq
类似背包的\(dp\),单独考虑异或和与两种操作,由于与运算没有逆运算,所以刷表\(dp\)转移.
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mo = 1e9 + 7;
ll dp1[1010][2021],dp2[1010][2020],ans;
int a[1010];
int n;
ll mul(ll a,ll b){
ll ans = 0,base = a;
while(b != 0){
if(b & 1)ans = (ans + base) % mo;
base = (base + base)% mo;
b >>= 1;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n ;i ++){
scanf("%d",&a[i]);
dp2[i][a[i]] = 1;
dp1[i][a[i]] = 1;
}
for(int i = 2;i <= n ;i ++){
for(int j = 0;j <= 1025;j ++){
dp1[i][j^a[i]] = (dp1[i][j^a[i]]%mo + dp1[i-1][j]%mo)%mo;
dp1[i][j] = (dp1[i][j]%mo + dp1[i-1][j]%mo)%mo;
}
}
for(int i = n - 1;i >= 1; i --){
for(int j = 0;j <= 1025;j ++){
dp2[i][j&a[i]] = (dp2[i][j&a[i]]%mo + dp2[i+1][j]%mo)%mo;
dp2[i][j] = (dp2[i][j]%mo + dp2[i+1][j]%mo)%mo;
}
}
for(int i = 1;i <= n ;i ++){
for(int s = 0;s <= 1024;s ++){
ans = (ans + (dp1[i][s] - dp1[i-1][s] + mo)%mo*dp2[i + 1][s]%mo)%mo;
}
}
cout<<ans<<endl;
return 0;
}
T3.travel
一道有趣的最短路
当T很大的时候,我们考虑 如果\(0 -> x -> n - 1\)路径时间为T,且 从x出发有一个时间为d的环,则 一定存在一个K满足 \(K + p * d = T\)(至少T满足条件),这样我们就能绕着环走p次就能构成一条时间为T的路径。
显然要求的路径一定经过 0,而且在合法情况下从0号点出发一定存在一条边,否则0号点和n - 1号就是不联通的。随便取一条边时间为d, 则能构成从0号点出发的一个时间为2d的环。
这样原题就化为最短路问题了,dis[i][j] 代表到达i号点,时间为 \(j + p * 2d\),最小的 \(j+p * 2d\),
最后判断dis[n -1][T % 2d] 是否小于等于T即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 210;
int n,m,T;
ll t,d,dis[maxn][20001],q2[maxn*10000];
int l,pre[maxn],last[maxn],other[maxn],len[maxn];
int q1[maxn*10000],vis[maxn][20010];
void add(int x,int y,int z){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
void spfa(){
memset(dis,0x3f,sizeof dis);
memset(q1,0,sizeof q1);
memset(q2,0,sizeof q2);
dis[0][0] = 0;
q1[1] = 0,q2[1] = 0;
int h = 0,t = 1;
while(h != t){
h ++;
int u = q1[h],f = q2[h];
vis[u][f] = 0;
for(int p = last[u];p; p = pre[p]){
int v = other[p];
if(dis[v][(f + len[p])%d] > dis[u][f] + len[p]){
dis[v][(f + len[p])%d] = dis[u][f] + len[p];
if(vis[v][(f + len[p]) % d])continue;
vis[v][(f + len[p])%d] = 1;
t ++;
q1[t] = v;q2[t] = (f + len[p])%d;
}
}
}
}
int main()
{
scanf("%d",&T);
while(T --){
scanf("%d%d%lld",&n,&m,&t);
l = 0;
memset(last,0,sizeof last);
for(int i = 1;i <= m ;i ++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);add(b,a,c);
}
d = 1e9 + 7;
if(last[0] == 0){
cout<<"Impossible"<<endl;continue;
}
for(int p = last[0];p;p = pre[p]){
d = min(d,(ll)len[p]);
}
d *= 2;
spfa();
if(dis[n - 1][(int)(t % d)] <= t){
cout<<"Possible"<<endl;
}
else cout<<"Impossible"<<endl;
}
return 0;
}
改造二叉树
中序遍历 + LIS
数字对
st表 + 二分
T1-A. 【线上训练 8】闯关
题解:一道贪心,由于题目保证存在合法解,所以难度肯定是呈段递增的,又由于保证挑选关卡时编号小的在前,所以直接分段贪心即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int ans[101001],cnt = 1;
int vis[101000],f[101010];
int p[101010];
int n;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n ;i ++){
scanf("%d",&p[i]);
}
for(int i = 1;i <= n;i ++){
f[p[i]] = cnt;
if(p[i] > p[i + 1] && i != n)cnt ++;
}
for(int i = 1;i <= n ;i ++){
if(vis[f[i]] == 0){
ans[i] = f[i];
vis[f[i]] = 1;
}
else ans[i] = ++cnt;
}
for(int i = 1;i <= n ;i ++)
cout<<ans[i]<<" ";
return 0;
}
T2-B. 【线上训练 8】动物园
题解:一道非常好的带权并查集,可以发现答案的总方案是固定的,所以可以将方案数转化为概率,由于主场优势,所以主场有\(\frac{2}{3}\)的胜率,客场有\(\frac{1}{3}\)的胜率,对于并查集,我们也可以像线段树一样打懒标记,来维护答案对于根节点的标记要路径压缩完后再下放,避免重复
T3-C. 【线上训练 8】树
30pts做法:暴力建树,跑树形dp.
60pts做法:对于\(T_{i-1}\)我们不需要建出树来,可以直接把上回树形dp的结果直接附过来,再跑树形dp即可。
100pts做法:咕咕咕咕
60pts代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int mo = 998244353;
const int maxn = 1e5 + 7;
int n,m;
int tmp1,tmp2;
int dp[maxn][2],fr;
int l,last[maxn],other[maxn<<1],pre[maxn<<1];
void add(int x,int y){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
}
void dfs(int x,int fa){
dp[x][1] = (tmp2 + 1)%mo;dp[x][0] = max(tmp1,tmp2);
for(int p = last[x];p;p = pre[p]){
int v = other[p];
if(v == fa)continue;
// cout<<x<<" "<<v<<endl;
dfs(v,x);
dp[x][0] = (dp[x][0] + max(dp[v][0],dp[v][1])%mo)%mo;
dp[x][1] = (dp[x][1] + dp[v][0])%mo;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i < n;i ++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,0);
cout<<max(dp[1][0] ,dp[1][1])<<endl;
fr = 1;
for(int i = 1;i <= m ;i ++){
int k;
scanf("%d",&k);
tmp1 = dp[fr][1];
tmp2 = dp[fr][0];
memset(dp,0,sizeof dp);
dfs(k,0);
cout<<max(dp[k][0],dp[k][1])%mo<<endl;
fr = k;
}
return 0;
}
T1路径
题解:如图,树本质上就可以分解成这样的若干条链,若只在链上长度小于等于2就可以,在树上的话就需要小于等于3,题目也正满足条件,所以遇到深度为奇数直接输出,否则回溯时在输出。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 7;
int n;
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
int dep[maxn];
void add(int x,int y){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
}
void dfs(int x,int fa){
if(dep[x] % 2 == 1)cout<<x<<" ";
for(int p = last[x];p;p = pre[p]){
int v = other[p];
if(v == fa)continue;
dep[v] = dep[x] + 1;
dfs(v,x);
}
if(dep[x] % 2 == 0)cout<<x<<" ";
}
int main()
{
scanf("%d",&n);
for(int i = 1;i < n ;i ++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
cout<<"Yes"<<endl;
dep[1] = 1;
dfs(1,0);
return 0;
}
T2魔法
(我好菜,基本\(dp\)都想不出)
题解:首先kmp预处理每个子串,考虑dp,设计dp[i]表示i这个位置去掉的后的最小代价,直接转移的话就直接枚举上一次的位置,保证i,j之间没有子串,可以通过kmp处理出每个位置的最小左界,复杂度\(O(n^{2})\)。通过线段树再次优化变为\(O(nlogn)\)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
int n,m;
char s[11][maxn],T[maxn];
int cnt,l[maxn];
int a[maxn],dp[maxn];
int nxt[maxn];
struct node{
int l,r,mn;
}t[maxn*4];
void kmp(int len,int j){
int p = 0;
memset(nxt,0,sizeof nxt);
for(int i = 2;i <= len;i ++){
while(p && s[j][i] != s[j][p + 1])p = nxt[p];
if(s[j][i] == s[j][p + 1])p ++;
nxt[i] = p;
}
p = 0;
for(int i = 1;i <= n;i ++){
while(p && T[i] != s[j][p + 1]) p = nxt[p];
if(s[j][p + 1] == T[i]){
p ++;
if(p == len){
l[i] = max(l[i],i - len + 1);
p = nxt[p];
}
}
}
}
void build(int x,int l,int r){
t[x].l = l;t[x].r = r;
if(l == r){
return ;
}
int mid = l + r >> 1;
build(x+x,l,mid);build(x+x+1,mid + 1,r);
t[x].mn = min(t[x+x].mn,t[x+x+1].mn);
}
void change(int x,int y,int z){
if(t[x].l == y && t[x].r == y){
t[x].mn = z;
return ;
}
int mid = t[x].l + t[x].r >> 1;
if(y <= mid)change(x+x,y,z);
else change(x+x+1,y,z);
t[x].mn = min(t[x+x].mn,t[x+x+1].mn);
}
int ask(int x,int l,int r){
if(t[x].l == l && t[x].r == r){
return t[x].mn;
}
int mid = t[x].l + t[x].r >> 1;
if(r <= mid)return ask(x+x,l,r);
else if(l > mid)return ask(x+x+1,l,r);
else {
return min(ask(x+x,l,mid),ask(x+x+1,mid+1,r));
}
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",T + 1);
for(int i = 1;i <= n ;i ++){
scanf("%d",&a[i]);
}
for(int i = 1;i <= m;i ++){
scanf("%s",s[i] + 1);
}
for(int i = 1;i <= m ;i ++){
int k = strlen(s[i] + 1);
kmp(k,i);
}
int ans = 1e9 + 7;
memset(dp,0x3f,sizeof dp);
dp[0] = 0;
int p = 0;
build(1,1,n);
for(int i = 1;i <= n + 1;i ++){
p = max(p,l[i-1]);
int tmp = 0;
if(p != 0)tmp = ask(1,p,i - 1);
else tmp = 0;
dp[i] = tmp + a[i];
if(i != n + 1)change(1,i,dp[i]);
}
cout<<dp[n+1]<<endl;
return 0;
}
T2灵魂画师(paint)
期望DP,(我只会写暴力啊,637巨佬直接A了啊)。
\(dp\)考虑一个点被修改了\(i\)次,颜色为\(j\)的概率
\(dp[i][j*p]\) += \(dp[i-1][j]/2 * c\)
最后把概率乘上颜色,就是答案。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
int n,c,k;
int l,r;
double ans;
double dp[101][101];
int cnt[101],mx;
int main()
{
n = read(),c = read(),k = read();
for(int i = 1;i <= k;i ++){
int l = read(),r = read();
for(int j = l;j <= r;j ++)
cnt[j] ++,mx = max(mx,cnt[j]);
}
dp[0][1] = 1;
for(int i = 0;i <= mx;i ++){
for(int j = 0;j < c;j ++){
for(int p = 0;p < c;p ++){
dp[i+1][j*p%c] += dp[i][j]/(2.0*c);
}
dp[i+1][j] += dp[i][j]/2.0;
}
}
for(int i = 1;i <= n ;i ++){
for(int j = 0;j < c;j ++){
ans += dp[cnt[i]][j] * (double)j;
}
}
printf("%.3lf",ans);
return 0;
}
T1星空(star)
仔细观察,可以发现一个点是否必胜,取决于从它下方转移过来的三个点。这样既可dp,\(dp[i][j] = 1(dp[i-1][j] = 1 || dp[i][j-1] = 1||dp[i-1][j-1])\)
这样dp后打表,也可以发现规律,只有横纵坐标均为奇数才必输。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
int x;scanf("%d",&x);return x;
}
int n,m;
int dp[1010][1010];
int main()
{
for(int i = 2;i <= 1001;i += 2)
dp[1][i] = dp[i][1] = 1;
//dp[2][3] = 1;dp[3][2] = 1;dp[2][2] = 1;
for(int i = 2;i <= 1001 ;i ++){
for(int j = 2;j <= 1001 ;j ++){
if(dp[i-1][j] == 0||dp[i][j-1] == 0||dp[i-1][j-1] == 0)
dp[i][j] = 1;
}
}
while(1){
n = read(),m = read();
if(n == 0)return 0;
if(dp[n][m])cout<<"Yuri"<<endl;
else cout<<"Chito"<<endl;
}
return 0;
}
T2洗衣(cloth)
这里满分做法先咕掉了,先介绍70pts做法
先把树建出来,对于答案,考虑每条边的贡献,\(ans += len[p] * siz[v] * (all - siz[v])\);
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
int x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
const int mo = 1e9 + 7;
int l,pre[61][maxn<<1],last[51][maxn];
struct node{
int x,y,z;
}e[61][maxn<<1];
int m;
ll ans;
int siz[maxn],cnt[61],sum[61];
void add(int p,int x,int y,int z){
l ++;
pre[p][l] = last[p][x];
last[p][x] = l;
e[p][l].x = x;
e[p][l].y = y;e[p][l].z = z;
}
void dfs(int q,int x,int fa){
siz[x] = 1;
for(int p = last[q][x];p;p = pre[q][p]){
int v = e[q][p].y;
if(v == fa)continue;
dfs(q,v,x);
siz[x] += siz[v];
ans = (ans + 1ll*e[q][p].z%mo*1ll*siz[v]%mo*1ll*(sum[q] - siz[v])%mo)%mo;
}
}
int main()
{
m = read();
sum[0] = 1;
for(int i = 1;i <= m ;i ++){
int a = read(),b = read(),c = read(),d = read(),w = read();
cnt[i] = cnt[a] + cnt[b] + 2;
sum[i] = sum[a] + sum[b];
l = 0;ans = 0;
for(int j = 1;j <= cnt[a];j ++)
add(i,e[a][j].x,e[a][j].y,e[a][j].z);
for(int j = 1;j <= cnt[b];j ++)
add(i,e[b][j].x + sum[a],e[b][j].y + sum[a],e[b][j].z);
add(i,c,d + sum[a],w);add(i,d+sum[a],c,w);
memset(siz,0,sizeof siz);
dfs(i,0,-1);
printf("%lld\n",ans%mo);
}
return 0;
}
T3无题(noname)
这题有好多部分分啊,会了线段树就有50pts,会了主席树就有了70pts
正解:由于$k <= 10 $,用线段树维护前十大,合并上传时,就类似于归并一样的操作。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
int x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
struct ten{
int w[12];
};
struct node{
int l,r,lazy,mx;
ten in;
}t[4*maxn];
int cnt,n,q;
int a[maxn];
void update(int x){
int p1 = 1,p2 = 1;
for(int i = 1;i <= 10;i ++){
if(t[x+x].in.w[p1] > t[x+x+1].in.w[p2]){
t[x].in.w[i] = t[x+x].in.w[p1];
p1 ++;
}
else {
t[x].in.w[i] = t[x+x+1].in.w[p2];
p2 ++;
}
// cout<<t[x].in.w[i]<<endl;
}
}
void build(int x,int l,int r){
t[x].l = l;t[x].r = r;
if(l == r){
t[x].in.w[1] = a[l];
return ;
}
int mid = l + r >> 1;
build(x+x,l,mid);build(x+1+x,mid+1,r);
update(x);
}
void motify(int x){
for(int i = 1;i <= 10;i ++)
if(t[x+x].in.w[i])t[x+x].in.w[i] += t[x].lazy;
for(int i = 1;i <= 10;i ++)
if(t[x+x+1].in.w[i])t[x+x+1].in.w[i] += t[x].lazy;
t[x+x].lazy += t[x].lazy;
t[x+x+1].lazy += t[x].lazy;
t[x].lazy = 0;
}
void change(int x,int l,int r,int z){
if(t[x].l == l && t[x].r == r){
for(int i = 1;i <= 10;i ++){
if(t[x].in.w[i])
t[x].in.w[i] += z;
else break;
}
t[x].lazy += z;
return ;
}
if(t[x].lazy)motify(x);
int mid = t[x].l + t[x].r >> 1;
if(r <= mid)change(x+x,l,r,z);
else if(l > mid)change(x+x+1,l,r,z);
else{
change(x+x,l,mid,z);change(x+x+1,mid+1,r,z);
}
update(x);
}
ten ask(int x,int l,int r){
if(t[x].l == l && t[x].r == r){
return t[x].in;
}
if(t[x].lazy)motify(x);
int mid = t[x].l + t[x].r >> 1;
if(r <= mid)return ask(x+x,l,r);
else if(l > mid)return ask(x+x+1,l,r);
else {
int p1 = 1,p2 = 1;
ten tmp1 = ask(x+x,l,mid),tmp2 = ask(x+x+1,mid+1,r),ans;
for(int i = 1;i <= 10;i ++){
if(tmp1.w[p1] > tmp2.w[p2]){
ans.w[i] = tmp1.w[p1];
p1 ++;
}
else {
ans.w[i] = tmp2.w[p2];
p2 ++;
}
//cout<<ans.w[i]<<endl;
}
return ans;
}
}
int main()
{
scanf("%d%d",&n,&q);
for(int i = 1;i <= n ;i ++)
scanf("%d",a+i);
build(1,1,n);
for(int i = 1;i <= q;i ++){
int opt = read(),l = read(),r = read(),x = read();
if(opt == 1){
change(1,l,r,x);
}
else {
if(x > r - l + 1){cout<<-1<<endl;continue;}
ten ans = ask(1,l,r);
printf("%d\n",ans.w[x]);
}
}
return 0;
}
T2区间(range)
菜鸡的我只会写暴力,咕咕咕。
正解:and运算是单调非增,or运算是单调非减,利用st表一样的想法,查询区间值,由于多and,或者or相同的数,并不影响结果,所以是正确的。利用二分的思想差找符合答案的区间,记录答案即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int mo = 1e9 + 7;
int ast[maxn][21],hst[maxn][21];
int n,a,b,c,d;
int s[maxn],ans;
int cha1(int l,int r){
int j = log(r - l + 1)/log(2);
return ast[l][j] & ast[r - (1 << j) + 1][j];
}
int cha2(int l,int r){
int j = log(r - l + 1)/log(2);
return hst[l][j] | hst[r - (1 << j) + 1][j];
}
void pre(){
for(int j = 1;j <= 19;j ++){
for(int i = 1;i <= n &&i + (1 << j) - 1<= n;i ++){
ast[i][j] = ast[i][j-1]&ast[i + (1 << (j-1))][j-1];
hst[i][j] = hst[i][j-1]|hst[i + (1 << (j-1))][j-1];
}
}
}
int main()
{
scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
for(int i = 1;i <= n ;i ++)
scanf("%d",&s[i]),ast[i][0] = s[i],hst[i][0] = s[i];
pre();
for(int i = 1;i <= n ;i ++){
int l = i,r = n,cnt1 = 0,cnt2 = 0,cnt3 = 0,cnt4 = 0;
while(l <= r){
int mid = l + r >> 1;
int x = cha1(i,mid);
if(x >= a)cnt2 = mid,l = mid + 1;
else r = mid - 1;
}
l = i,r = n;
while(l <= r){
int mid = l + r >> 1;
int x = cha1(i,mid);
if(x > b)l = mid + 1;
else r = mid - 1,cnt1 = mid;
}
l = i,r = n;
while(l <= r){
int mid = l + r >> 1;
int x = cha2(i,mid);
if(x >= c)cnt3 = mid,r = mid - 1;
else l = mid + 1;
}
l = i,r = n;
while(l <= r){
int mid = l + r >> 1;
int x = cha2(i,mid);
if(x > d)r = mid - 1;
else l = mid + 1,cnt4 = mid;
}
if(cnt1&&cnt2&&cnt4&&cnt3){
int L = max(cnt1,cnt3);
int R = min(cnt2,cnt4);
if(R >= L)ans += R - L + 1,ans %= mo;
}
}
printf("%d",ans);
return 0;
}
T3 收集果子(fruit)
树形DP,一眼树上背包,然而不会转移(咕咕咕,我好菜)
设计状态\(dp[i][j]\),\(i\)的子树中选了\(j\)个果子。
\(dp[x][i+j] += dp[x][i] * dp[v][j]\);
如果\(dp[x][j](j == 0)\)时
\(dp[x][0] = dp[x][0] * pow(2,siz[x]-1)\);
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
char ch=getchar();int x=0,f=1;
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch<='9' && ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline ll readl()
{
char ch=getchar();ll x=0,f=1;
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch<='9' && ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int mo = 1e9 + 7;
const int maxn = 2e3 + 7;
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
int n,k,w[maxn];
ll dp[maxn][maxn],siz[maxn],sum[maxn];
void add(int x,int y){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
}
ll qp(int a,int b){
ll ans = 1,base = a;
while(b != 0){
if(b & 1)ans = ans * base % mo;
base = base * base %mo;
b >>= 1;
}
return ans;
}
void dfs(int x,int fa){
sum[x] = w[x];dp[x][w[x]] = 1,siz[x] = 1;
for(int p = last[x];p;p = pre[p]){
int v = other[p];
if(v == fa)continue;
dfs(v,x);
dp[v][0] = (dp[v][0] + qp(2,siz[v] - 1))%mo;
for(int i = sum[x];i >= 0;i --){
if(dp[x][i] == 0)continue;
for(int j = sum[v];j >= 1; j--){
dp[x][i+j] = (dp[x][i+j] + 1ll*dp[x][i]*dp[v][j]%mo)%mo;
}
dp[x][i] = (1ll*dp[x][i] * dp[v][0])%mo;
//cout<<dp[x][i]<<" "<<x<<" "<<i<<endl;
}
siz[x] += siz[v];
sum[x] += sum[v];
}
}
int main()
{
n = read(),k = read();
for(int i = 1;i <= n ;i ++)w[i] = read();
for(int i = 1;i < n ;i ++){
int a = read(),b = read();
add(a,b);add(b,a);
}
dfs(1,0);
cout<<dp[1][k]<<endl;
return 0;
}
T1数列(seq)
(脑子被挤了啊)
题解:
每次两个数互相减,其实就相当于更相减损,于是就可以利用辗转相除。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
ll a1,a2;
ll ans;
void work(ll a,ll b){
if(b == 0)return ;
//cout<<a<<" "<<b<<endl;
ans += a/b;
work(b,a % b);
}
int main()
{
scanf("%lld%lld",&a1,&a2);
if(a1 < a2)swap(a1,a2);
work(a1,a2);
cout<<ans + 1<<endl;
return 0;
}
T2曹将军的战士
一道原题,luogu三元上升子序列
枚举中间节点,利用树状数组查询左右两侧符合要求的数,利用乘法原理再计算答案。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int n;
int a[maxn],l[maxn],r[maxn],c[maxn];
ll ans;
void add(int x,int y){
for(;x <= maxn;x += x&(-x))c[x] += y;
}
int ask(int x){
int tot = 0;
for(;x > 0;x -= x&(-x))tot += c[x];
return tot;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n ;i ++){
scanf("%d",&a[i]);
}
for(int i = 1;i <= n ;i ++){
// cout<<a[i]<<endl;
l[i] = ask(maxn) - ask(a[i]);
add(a[i],1);
}
memset(c,0,sizeof c);
for(int i = n;i >= 1; i--){
r[i] = ask(a[i]-1);
add(a[i],1);
}
for(int i = 1;i <= n ;i ++){
ans += (ll)l[i] * (ll)r[i];
}
cout<<ans<<endl;
return 0;
}
T3 车辆销售(car)
考试时一眼就觉得这题和NOI2018归程很像,想了想以为应该不会是kruscal重构树,就随便糊了个暴力,结果它真就是个重构树。(猝死)
重构树性质是很多的,这道题就很简单了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
int read(){
int x;scanf("%d",&x);return x;
}
typedef long long ll;
const int maxn = 2e5 + 7;
int n,m;
int l,pre[maxn<<1],last[maxn<<1],other[maxn<<1];
int siz[maxn<<1],val[maxn<<1],fa[maxn<<1];
ll ans[maxn];
struct node{
int a,b,c;
}p[maxn*3];
bool cmp(node a,node b){
return a.c > b.c;
}
int get(int x){
if(x == fa[x])return x;
return fa[x] = get(fa[x]);
}
void add(int x,int y){
l ++;
pre[l] = last[x];
last[x] = l;
other[l] = y;
}
void dfs(int x,int fa,ll sum){
for(int p = last[x];p;p = pre[p]){
int v = other[p];
ll tmp = 0;
if(val[v] == val[x])siz[v] = siz[x];
else tmp += (ll)(siz[x] - siz[v])*(siz[x] - siz[v]);
ans[v] = sum + tmp;
dfs(v,x,sum + tmp);
}
}
int main()
{
n = read(),m = read();
for(int i = 1;i <= m;i ++){
p[i].a = read(),p[i].b = read(),p[i].c = read();
}
sort(p + 1,p + 1 + m,cmp);
int cnt = n;
for(int i = 1; i <= n ;i ++){
fa[i] = i;siz[i] = 1;
}
for(int i = 1;i <= m ;i ++){
int fx = get(p[i].a),fy = get(p[i].b);
if(fx == fy)continue;
cnt ++;
add(cnt,fx);
add(cnt,fy);
fa[fx] = cnt;
fa[fy] = cnt;
fa[cnt] = cnt;
siz[cnt] = siz[fx] + siz[fy];
val[cnt] = p[i].c;
}
dfs(cnt,0,0);
for(int i = 1;i <= n ;i ++)cout<<ans[i]<<" ";
return 0;
}
CometOJ Day1
T1计算机 (computer)
比较简单,主要就是看出变成3会很优(众所周知,oier大胆猜想无须证明)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
inline ll readl(){
ll x;scanf("%lld",&x);return x;
}
const int mo = 1e9 + 7;
ll n,ans;
ll qp(ll a,ll b){
ll ans = 1,base = a;
while(b != 0){
if(b & 1)ans = ans * base % mo;
base = base * base%mo;
b >>= 1;
}
return ans;
}
int main()
{
n = readl();
if(n == 1){
cout<<1<<endl;return 0;
}
if(n == 2){
cout<<2<<endl;return 0;
}
if(n == 4){
cout<<4;return 0;
}
if(n % 3 == 0){
ans = qp(3,n/3);
cout<<ans<<endl;
return 0;
}
if(n % 3 == 2){
ans = qp(3,n/3) * 2 % mo;
cout<<ans<<endl;
return 0;
}
if(n % 3 == 1){
ans = qp(3,n/3 - 1) * 4 % mo;
cout<<ans<<endl;
}
return 0;
}
T2探险 (adventure)
恶心模拟题,一堆特判,主要考虑形如“.#.”,“.# * ”而自己只有一个或0个时,可以先放这里走过去再等之后又有‘* ’再回来捡。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
inline ll readl(){
ll x;scanf("%lld",&x);return x;
}
const int maxn = 4e6 + 7;
int T;
char s[maxn];
int main()
{
T = read();
while(T --){
int ans = 0,sum = 0,tmp = 0,cur = 0;
scanf("%s",s + 1);
int len = strlen(s + 1);
for(int i = 1;i <= len;i ++){
if(s[i] == '#')
{
if(i == len)break;
if(s[i+1] == '#' && s[i+2] == '.')break;
if(s[i+1] == '#' && s[i+2] == '#')break;
if(s[i+1] == '#' && sum == 0)break;
if(sum == 0 && s[i+1] != '*' && s[i-1] != '*')break;
if(s[i+1] == '.' && sum == 0)break;
if(s[i+1] == '#' && s[i+2] == '*' && sum == 0)break;
if(s[i+1] == '#' && s[i+2] == '*' && sum > 0)sum --;
if(s[i+1] == '.' && sum == 1)sum --,tmp ++;
if(s[i-1] == '.'&&s[i+1] == '*'&& sum == 0)cur += tmp,tmp = 0;
}
else if(s[i] == '*'){
sum ++;
if(sum >= 1 && tmp)sum += tmp,tmp = 0;
if(sum >= 2 && cur)sum += cur ,cur = 0;
}
// cout<<cur<<" "<<sum<<endl;
ans = max(ans,sum);
}
cout<<ans<<endl;
}
return 0;
}
T3高速公路 (highway)
先是树形DP求出树上每个子树下的最长链,直径,再对每个节点dp求出去掉子树i之后的直径,类似求每个点的树上最长路径。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
inline ll readl(){
ll x;scanf("%lld",&x);return x;
}
const int maxn = 2e5 + 7;
int n;
int son[maxn];
ll f[maxn],d[maxn];
ll w[maxn],dp[maxn][2],fr[maxn],nxt[maxn],g[maxn],ans,up[maxn],len[maxn<<1];
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
void add(int x,int y,int z){
l ++ ;
pre[l] = last[x];
last[x] = l;
other[l] = y;
len[l] = z;
}
void dfs1(int x,int fa)
{
for(int p = last[x];p;p = pre[p])
{
int v = other[p];
if(v == fa) continue;
dfs1(v,x);
f[x] = max(f[x],d[x] + d[v] + len[p]);
d[x] = max(d[x],d[v] + len[p]);
f[x] = max(f[x],f[v]);
// cout<<f[x]<<d[x]<<endl;
}
}
void dfs2(int x,int fa){
int cnt = 0;
for(int p = last[x];p;p = pre[p]){
int v = other[p];
if(v == fa)continue;
son[++cnt] = v;w[cnt] = len[p];
}
fr[0] = 0;nxt[cnt+1] = 0;
for(int i = 1;i <= cnt; i++){
fr[i] = max(fr[i-1],d[son[i]] + w[i]);
}
for(int i = cnt;i >= 1;i --){
nxt[i] = max(nxt[i+1],d[son[i]] + w[i]);
}
ll mx = -1e13 + 7;
for(int i = 1;i <= cnt;i ++){
up[son[i]] = max(up[son[i]],up[x] + w[i]);
up[son[i]] = max(up[son[i]],max(fr[i-1],nxt[i+1]) + w[i]);
g[son[i]] = max(g[son[i]],g[x]);
g[son[i]] = max(g[son[i]],fr[i-1] + nxt[i+1]);
g[son[i]] = max(g[son[i]],max(fr[i-1],nxt[i+1]) + up[x]);
g[son[i]] = max(g[son[i]],mx);
mx = max(mx,f[son[i]]);
}
for(int p = last[x];p;p = pre[p]){
int v = other[p];
if(v == fa)continue;
dfs2(v,x);
}
}
void clear(){
memset(d,0,sizeof d);
memset(f,0,sizeof f);
memset(g,0,sizeof(g));
memset(dp,0,sizeof(dp));
memset(up,0,sizeof(up));
memset(fr,0,sizeof(fr));
memset(nxt,0,sizeof(nxt));
memset(son,0,sizeof(son));
memset(w,0,sizeof(w));
}
int main()
{
n = read();
for(int i = 1;i < n ;i ++){
int a = read(),b = read(),c = read();
add(a,b,c);add(b,a,c);
}
dfs1(1,0);dfs2(1,0);
for(int i = 1;i <= n ;i ++)
ans = max(ans,(f[i])*g[i]);
for(int i = 1;i <= l;i ++)len[i] = -len[i];
clear();
dfs1(1,0);dfs2(1,0);
for(int i = 1;i <= n ;i ++)
ans = max(ans,(f[i])*g[i]);
cout<<ans<<endl;
return 0;
}
牛客比赛
T4
一道边界卡死人的DP.
考虑\(dp[i][j][0/1]\)表示到i为止,用了j次手动删除,最后一次用的什么删除
考虑转移
\(dp[i][j][0] = max\{dp[i-1][j][0/1] + b[i]\};\)
\(dp[i][j][1] = max\{dp[i-1][j-1][0] + a[i]\};\)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
int n,m;
ll a[maxn],b[maxn];
ll dp[3][1011][3];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++){
scanf("%lld%lld",&a[i],&b[i]);
}
for(int i = 1;i <= n ;i ++){
dp[i&1][0][1] = dp[(i-1)&1][0][1] + b[i];
for(int j = 1;j <= min(i-1,m);j ++){
dp[(i&1)][j][0] = dp[((i-1)&1)][j-1][1] + a[i];//0,shoudong,1,zidong
dp[(i&1)][j][1] = max(dp[((i-1)&1)][j][0],dp[(i-1)&1][j][1]) + b[i];
}
}
ll ans = -1;
for(int i = 0;i <= m;i ++){
ans = max(ans,dp[(n&1)][i][0]);
ans = max(ans,dp[(n&1)][i][1]);
}
printf("%lld",ans);
return 0;
}
LY的模拟赛
T3网络(net)
最小生成树 + 01背包
T2信仰(sanae)
神仙DP。(到死都觉得是个贪心)
dp[i][j]表示前i天找了j个妖精的最小时间。
for(int i = 1;i <= n ;i ++){
for(int j = 1;j <= i;j ++){
int tmp = max(l[i],dp[i-1][j-1]);
if(tmp + t[i] > r[i])dp[i][j] = dp[i-1][j];
else dp[i][j] = min(dp[i-1][j],tmp + t[i]);
}
}
清北模拟赛
T1
正难则反,暴力搜索不合法的
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
inline ll readl(){
ll x;scanf("%d",&x);return x;
}
int cnt1;
int chose[11];
int l,r,ans,cnt;
void dfs(int x,int y,int len){
if(y > r)return ;
if(y >= l && y <= r && x > len){
// cout<<y<<endl;
ans ++;
return ;
}
for(int i = 0;i <= 9;i ++){
if(!y && i == 0)continue;
if(chose[i] == i - 1)continue;
chose[i] ++;
dfs(x + 1,y*10 + i,len);
chose[i] --;
}
}
int main()
{
//freopen("number1.in","r",stdin);
//freopen("number1.out","w",stdout);
l = read(),r = read();
int r1 = r,l1 = l;
while(l1 != 0){
cnt1 ++;
l1 /= 10;
}
while(r1 != 0){
cnt ++;
r1 /= 10;
}
for(int i = cnt1;i <= cnt;i ++)
dfs(1,0,i);
cout<<r - l + 1 - ans<<endl;
return 0;
}
T2
有趣的树上差分。
先搞出遍历顺序,由于答案加上\(max(size[i]-1,0) * z[i]\),所以对于起点,要在其父节点上++,剩下的就是普通的树上差分
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
int x;scanf("%d",&x);return x;
}
inline ll readl(){
ll x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
int dfn[maxn],cf[maxn],f[maxn][20],id[maxn];
int x[maxn],y[maxn],z[maxn],dep[maxn],cnt;
int n,m;
ll ans,mx,sum[maxn];
vector <int> p[maxn];
void dfs(int x,int fa){
for(int i = 0;i < p[x].size();i ++){
int v = p[x][i];
if(v == fa)continue;
dep[v] = dep[x] + 1;
f[v][0] = x;
dfs(v,x);
}
dfn[x] = ++cnt;
id[cnt] = x;
}
void dfs1(int x,int fa){
sum[x] = cf[x];
for(int i = 0;i < p[x].size();i ++){
int v = p[x][i];
if(v == fa)continue;
dfs1(v,x);
sum[x] += sum[v];
}
}
int lca(int u,int v){
if(dep[u] < dep[v])swap(u,v);
for(int i = 0;i <= 17;i ++){
if((dep[u] - dep[v]) & (1 << i))u = f[u][i];
}
if(u == v)return u;
for(int i = 17;i >= 0;i --){
if(f[u][i] != f[v][i]){
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
int main()
{
freopen("wind.in","r",stdin);
freopen("wind.out","w",stdout);
n = read(),m = read();
for(int i = 1;i < n;i ++){
int a = read(),b = read();
p[a].push_back(b);
p[b].push_back(a);
}
for(int i = 1;i <= n ;i ++){
sort(p[i].begin(),p[i].end());
}
dfs(1,0);
for(int j = 1;j <= 17;j ++){
for(int i = 1;i <= n;i ++)
f[i][j] = f[f[i][j-1]][j-1];
}
for(int i = 1;i <= m;i ++){
int x = read(),y = read(),z = read();
int c = lca(x,y);
//cout<<x<<" "<<y<<" "<<dfn[x]<<" "<<dfn[y]<<endl;
if(dfn[x] < dfn[y])cf[f[x][0]] += z,cf[y] += z;
else cf[f[y][0]] += z,cf[x] += z;
cf[c] -= z;cf[f[c][0]] -= z;
}
dfs1(1,0);
// for(int i = 1;i <= n;i ++)cout<<sum[i]<<" ";
for(int i = 1;i <= cnt;i ++){
ans += sum[id[i]];
ans -= dep[id[i]];
mx = max(mx,ans);
}
cout<<mx<<endl;
return 0;
}
T1
简单说一下题面
T组数据,查找l,r区间内满足
1.是一个质数
2.可以被分解为两个质数
\(T<=1e5,l,r<=1e7.\)
这么大的数据范围我们首先想到线性筛,并且在线性筛的基础上,把性质2的数判断出来,到这里这题已经完成一半了。继续思考发现我们要在\(O(1)\)的时间内把答案判断出来,所以我们可以通过前缀和进行优化,来做到\(O(1)\)时间回答。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
bool vis[10000001],flag[10000001];
int prime[10000001],cnt,T,ans[10000001];
void make_prime(int x)
{
memset(vis,1,sizeof vis);
vis[1]=0;flag[1]=0;
for(int i=2;i<=x;i++)
{
if(vis[i]==1)
{
prime[++cnt]=i;
}
for(int j=1;j<=cnt&&i*prime[j]<=x;j++)
{
vis[i*prime[j]]=0;
if(vis[i]==1)flag[i*prime[j]]=1;//性质2
if(!(i%prime[j]))break;
}
}
return ;
}
int main()
{
//freopen("prime.in","r",stdin);
//freopen("prime.out","w",stdout);
scanf("%d",&T);make_prime(10000000);
ans[1]=0;ans[2]=1;
for(int i=3;i<=10000000;i++)
{
if(vis[i]==1||flag[i]==1)ans[i]=ans[i-1]+1;
else ans[i]=ans[i-1];
}
while(T--)
{
int l,r;
scanf("%d%d",&l,&r);
cout<<ans[r]-ans[l-1]<<endl;
}
return 0;
}
T2
【题目描述】
密室中有 N 个房间,初始时在1 号房间,而出口在 N 号房间。
密室的每一个房间中可能有着一些钥匙和一些传送门,一个传送门会单向地创造一条
从房间 X 到房间 Y 的通道。
另外,想要通过某个传送门,就必须具备一 些种类的钥匙。
幸运的是,钥匙在打开传送门的封印后,并不会消失。
然而,通过密室的传送门需要耗费大量的时间,因此希望通过尽可能少的传
送门到达出口
另外不能逃出这个密室,如果是这样,请输出“No Solution”。
第一行三个整数 N、M、K,分别表示房间的数量、传送门的数量以及钥匙的种类
数。
接下来 N 行,每行 K 个 0 或 1,若第 i 个数为 1,则表示该房间内有第 i 种钥
匙,若第 i 个数为 0,则表示该房间内没有第 i 种钥匙。
接下来 M 行,每行先读入两个整数 X,Y,表示该传送门是建立在 X 号房间,通向
Y 号房间的,再读入 K 个 0 或 1,若第 i 个数为 1,则表示通过该传送门需 要 i 种钥匙,若第 i 个数为 0,则表示通过该传送门不需要第 i 种钥匙。
这是一到状压bfs
压缩钥匙,并开一维记录,通过位运算对钥匙进行操作。
填坑
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=6005;
int l,pre[maxn],other[maxn],last[maxn],len[maxn];
int que[6000005],n,m,k;
int val[maxn],cur[6000005],ans[maxn][4000];
void add(int x,int y,int z)
{
l++;
pre[l]=last[x];
last[x]=l;
other[l]=y;
len[l]=z;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++)
{
int cur;
for(int j = 0; j < k; j++)
{
scanf("%d", &cur);
val[i] += cur << j;
}
}
for(int i = 1; i <= m; i++)
{
int x,y,curr,c=0;
scanf("%d%d", &x, &y);
for(int j = 0; j < k; j++){
scanf("%d", &curr);
c += curr << j;
}
add( x, y, c);
}
memset(ans, -1, sizeof(ans));
int h=0,t=1;
ans[1][val[1]]=0;
que[1]=1;cur[1]=val[1];
while(h<=t)
{
h++;
int u = que[h];
for(int p = last[u] ;p; p = pre[p])
{
int v = other[p];
if((cur[h] & len[p]) == len[p] && ans[v][cur[h] | val[v]] == -1)
{
t++;
que[t] = v;
cur[t] = cur[h]|val[v];
ans[v][cur[t]] = ans[u][cur[h]]+1;
if(v == n)
{
cout<<ans[v][cur[t]]<<endl;
return 0;
}
}
}
}
cout<<"No Solution"<<endl;
return 0;
}
T3
【题目描述】
城市 X 城是一个含有 N 个节点的无向图, X 城只建造 N – 1 条边,使得城
市的各个地点能够相互到达。你计划蒸发 Q 天的学水,每一天caiyingqi从 A 地走
到 B 地,并在沿 途各个地点留下一个水塘。此后,你会从 C 地走到 B 地,
蒸发沿途的水塘。由于 X 城是一个学%横行的城市,caiyingqi留下的水塘即使没有被蒸发,也会在第二天之前蒸发殆尽。 现在,你想要知道,你每一天能够蒸发多
少水塘呢?
可以看出这是一道lca题,求两条路径有多少公共点呢
暴力想法:树上染色,求公共点,可以用树上差分解决,复杂度为
$ O(q(n+logn)) $ 约为\(O(n * q)\)。
显然无法解决2e5的n和q。
正解:我们可以画几组样例发现答案一定是任意两点间lca中深度最深的点,与b间的距离,这里就不加以证明,各位可以自行脑补证明。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=400001;
int pre[maxn],other[maxn],last[maxn],l,n,q,num;
int dep[maxn];
int f[maxn][20];
void add(int x,int y)
{
l++;
pre[l]=last[x];
last[x]=l;
other[l]=y;
}
void dfs(int u)
{
for(int p=last[u];p;p=pre[p])
{
int v=other[p];
if(v==f[u][0])continue;
dep[v]=dep[u]+1;
f[v][0]=u;
dfs(v);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
for(int i=0;i<=16;i++)
if((dep[u]-dep[v])&(1<<i))u=f[u][i];
if(u==v)return u;
for(int j=17;j>=0;j--)
{
if(f[v][j]!=f[u][j])
{
u=f[u][j];
v=f[v][j];
}
}
return f[u][0];
}
int main()
{
scanf("%d%d%d",&n,&q,&num);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}
dfs(1);
for(int j=1;j<=17;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=1;i<=q;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int ans=1;
int x=lca(a,b);int y=lca(b,c);int z=lca(a,c);
if(dep[x]>=dep[y]&&dep[x]>dep[z])
ans+=dep[x]+dep[b]-2*dep[lca(x,b)];
else if(dep[y]>dep[x]&&dep[y]>dep[z])
ans+=dep[y]+dep[b]-2*dep[lca(y,b)];
else if(dep[z]>=dep[x]&&dep[z]>=dep[y])
ans+=dep[z]+dep[b]-2*dep[lca(z,b)];
cout<<ans<<endl;
}
return 0;
}
原文地址:https://www.cnblogs.com/wtz2333/p/12237309.html