2021.12.4 B组 总结
赛时:
T1:试过直接找性质,但看不出;试过打表,但没耐心;
T2: 想到DP,但不知道怎么设状态和转移
T3: 只是粗看,没有细想(在讲题的时候才发现看错条件,TT)
T4: 没时间去想了(可能是对最后一道题的不自信,以及时间确实不够了,一般不做最后一题)
题解:
T1:
考虑n的序列: n S(n) S(S(n)) …… 2
根据S(n)的定义可知 ∀ 整 数 i ∈ [ 1 , S ( n ) − 1 ] , i ∣ n \forall 整数i\in[1,S(n)-1],i|n ∀整数i∈[1,S(n)−1],i∣n, 即 l c m ( 1 , 2 , … , S ( n ) − 1 ) ∣ n lcm(1,2,\ldots,S(n)-1)|n lcm(1,2,…,S(n)−1)∣n,且 S ( n ) ∤ n S(n)\nmid n S(n)∤n
想到令 n = l c m ( 1 , 2 , … , S ( n ) − 1 ) ∗ t , 则 ( S ( n ) / g c d ( l c m ( 1 , 2 , … , S ( n ) − 1 ) , S ( n ) ) ) ∤ t n=lcm(1,2,\ldots,S(n)-1)*t,则(S(n)/gcd(lcm(1,2,\dots,S(n)-1),S(n)))\nmid t n=lcm(1,2,…,S(n)−1)∗t,则(S(n)/gcd(lcm(1,2,…,S(n)−1),S(n)))∤t,
发现由于 n < = 1 0 17 , n<=10^{17}, n<=1017,所以S(n)最大为41(自己算一下)。因此,考虑直接预处理出1~41的S(n),
然后计数S(n)相同数的个数。容易发现,不会算重,直接计数即可。
T2:
这篇博客不错
补充其中一些细节:
设i 位置上的数为a[i], 前半块和后半块的大小均为p
对于这样的情况,整个块中数的大小一定 ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 2 ) ] , k ∈ Z ∗ \in[p*2k+1,p*(2k+2)],k\in Z^* ∈[p∗2k+1,p∗(2k+2)],k∈Z∗,又因为每块中数字要连续,
所以 1 当 a [ i ] ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 1 ) ] a[i]\in [p*2k+1,p*(2k+1)] a[i]∈[p∗2k+1,p∗(2k+1)]时, a [ i − 1 ] ∈ [ p ∗ ( 2 k + 1 ) + 1 , p ∗ ( 2 k + 2 ) ] a[i-1]\in [p*(2k+1)+1,p*(2k+2)] a[i−1]∈[p∗(2k+1)+1,p∗(2k+2)]
2 当 a [ i − 1 ] ∈ [ p ∗ 2 k + 1 , p ∗ ( 2 k + 1 ) ] a[i-1]\in [p*2k+1,p*(2k+1)] a[i−1]∈[p∗2k+1,p∗(2k+1)]时, a [ i ] ∈ [ p ∗ ( 2 k + 1 ) + 1 , p ∗ ( 2 k + 2 ) ] a[i]\in [p*(2k+1)+1,p*(2k+2)] a[i]∈[p∗(2k+1)+1,p∗(2k+2)]
即 令
l=a[i]/p+(a[i]%p?0:1); if(l&1) l++; else l--;
a [ i − 1 ] ∈ [ p ∗ ( l − 1 ) + 1 , p ∗ l ] a[i-1]\in [p*(l-1)+1,p*l] a[i−1]∈[p∗(l−1)+1,p∗l]
p的取法很有深意,这样取可以使得刚好满足上述情况
T3:
暴力题,主要问题在于实现(也可以用tarjan缩点,从k出发求DAG的最长路,然而我不会)
看样例,发现2种情况
1.经过蹦床
蹦床覆盖范围的两种情况:
因为可以从蹦床出发可以跳到任意点,所以如果经过蹦床,答案即为所有蹦床范围和+最大的剩余的点的连续不降或不升子序列长度
2.不经过
答案为从k出发最长合法序列长度
实现:考虑分开经过蹦床的和不经过蹦床的
T4
发现,独立集对应的条件是: 选的序列单调递增(这样,集合中就不存在逆序对,即集合内的点不存在连边)
覆盖集条件: 设 选 的 点 集 为 V , a n 为 原 序 列 , 则 ∀ k ∈ ( i , j ) , i , j ∈ V , i < j , 满 足 a k < a i 或 a k > a j 设选的点集为V,a_n为原序列,则\forall k\in(i,j),i,j\in V,i<j,满足a_k<a_i或a_k>a_j 设选的点集为V,an为原序列,则∀k∈(i,j),i,j∈V,i<j,满足ak<ai或ak>aj
由于序列单调递增,所以只需要从 i-1 到 j ,维护 比a[i]小的a[k] 的最大值<a[j] 即可
对于第一个和最后一个数,可以通过强制选a[0]=0,a[n+1]=n+1,来简化转移;
现在问题变成了求原序列。
可以得到的信息有,与 i 位置上的数构成逆序对的数的个数(即点的度)
显然,逆序对来自于[1,i-1]和[i+1,n],两个同时考虑比较麻烦,有没有办法使得枚举时只要考虑一边
想到枚举时按从小到大或从大到小,可以只考虑一边,一下由从小到大考虑
i 位于未选择的位置的第 度数+1 个位置
至于为什么对任意一个合法序列进行转移都可以得到正确答案,应该是这些序列本质上都是同一个图
O(n2)
代码:
T1:
#include<cstdio>
#include<cstring>
#define ull unsigned long long
using namespace std;
ull A,B,k,sum1,sum2,f[50];
ull gcd(ull a,ull b){
return b?gcd(b,a%b):a;
}
ull work(ull a){
for(int i=2;i<a;i++)
if(a%i){
f[a]=f[i]+1;
break;
}
}
int main(){
scanf("%llu%llu",&A,&B);
k=1;A--;
f[2]=1;
for(ull i=2;i<=41;i++){
work(i);
ull cnt=A/k,d=gcd(k,i);
cnt=cnt-cnt/(i/d);sum2+=cnt*(f[i]+1);
cnt=B/k;cnt=cnt-cnt/(i/d);sum1+=cnt*(f[i]+1);
k=k*i/d;
// printf("%llu\n",k);
}
printf("%llu",sum1-sum2);
return 0;
}
T2:
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1024;
int k,v[N+5][N+5],f[N+5][N+5],ans=0x3f3f3f3f;
int min(int a,int b){
return a>b?b:a;
}
int main(){
memset(f,0x3f,sizeof f);
scanf("%d",&k);
for(int i=1;i<=(1<<k);i++)
for(int j=1;j<=(1<<k);j++)
scanf("%d",&v[i][j]);
for(int i=1;i<=(1<<k);i++) f[1][i]=0;
for(int i=2;i<=(1<<k);i++){
int p=k;
while(1 && p>0){
if((i-1)%(1<<p)==0 && i%(1<<p))
break;
p--;
}
for(int j=1;j<=(1<<k);j++){
int h=(j%(1<<p)?1:0)+(j>>p),l;
if(h&1) l=(1<<p)*(h+1);
else
l=(1<<p)*(h-1);
for(int o=1;o<=(1<<p);o++){
f[i][j]=min(f[i][j],f[i-1][l]+v[j][l]);
l--; if(i==1<<k) ans=ans>f[i][j]?f[i][j]:ans;
}
}
}
printf("%d",ans);
return 0;
}
T3:
#include<cstdio>
using namespace std;
const int N=300000;
int n,k,h[N+5],cf[N+5],p=0,sum=0,cnt=0,tot=1,vis[N+5],mx;//x : 0 单调不降
int max(int a,int b){
return a>b?a:b;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
for(int i=1;i<=n;i++){
char a;scanf(" %c",&a);
if(a=='T'){
int l=i-1,r=i+1;
while(h[l]>=h[l+1] && l>=1)
l--;
while(h[r]>=h[r-1] && r<=n)
r++;
if(l+1<=k && r-1>=k) p=1;
cf[l+1]+=1;cf[r]-=1;
}
}
int bj=0;h[0]=0x3f3f3f3f;tot=0;
for(int i=1;i<=n;i++){//单调不降
cnt=1;bj+=cf[i];
if(bj){
mx=max(tot,mx);
while(bj && i<=n){
i++;
bj+=cf[i];
}
if(i>n) break;
tot=1;i++;
}
if(h[i]>=h[i-1]) tot++;
else{
mx=max(tot,mx);
tot=1;
}
}
mx=max(tot,mx);
bj=0;h[0]=0x3f3f3f3f;tot=0;
for(int i=1;i<=n;i++){//单调不升
cnt=1;bj+=cf[i];
if(bj){
mx=max(tot,mx);
while(bj && i<=n){
i++;
bj+=cf[i];
}
if(i>n) break;
tot=1;i++;
}
if(h[i]<=h[i-1]) tot++;
else{
mx=max(tot,mx);
tot=1;
}
}
mx=max(tot,mx);
if(p){
int s=0;
for(int i=1;i<=n;i++){
s+=cf[i];
sum+=s?1:0;
}
// printf("%d %d ",mx,sum);
printf("%d",mx+sum);
}
else{
mx=0;tot=0;int l=k-1,r=k+1;cnt=1;
while(h[l]==h[l+1]){
l--;cnt++;
}
while(h[r]==h[r-1]){
r++;cnt++;
}
while(h[l]<=h[l+1] && l>=1){
tot++;l--;
}
mx=tot+cnt;tot=0;
while(h[r]<=h[r-1] && r<=n){
tot++;r++;
}
mx=max(mx,tot+cnt);
printf("%d",mx);
}
return 0;
}
T4:
#include<cstdio>
using namespace std;
const int M=1000000007;
const int N=1000;
int n,m,vis[N+5][N+5],du[N+5],xl[N+5],f[N+5];
int max(int a,int b){
return a>b?a:b;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
du[a]++;du[b]++;
vis[a][b]=1;vis[b][a]=1;
}
for(int i=1;i<=n;i++){//构造原序列
int cnt=0;
for(int j=1;j<=n;j++){
if(xl[j]) continue;
if(cnt>=du[i]){
xl[j]=i;
break;
}
cnt++;
}
for(int j=1;j<=n;j++){
if(j==i) continue;
if(vis[i][j]) du[j]--;
}
}
xl[0]=0;xl[n+1]=0x3f3f3f3f;f[0]=1;int mx;
for(int i=1;i<=n+1;i++){
mx=0;
for(int j=i-1;j>=0;j--){
if(mx<=xl[j] && xl[j]<xl[i])
f[i]=(f[i]+f[j])%M;
if(xl[i]>xl[j])
mx=max(mx,xl[j]);
}
}
printf("%d",f[n+1]);
return 0;
}
若有错误欢迎指出
禁止转载
12.7
update:
12.9 补充T2和T4