HDU暑假多校第一场
文章目录
[1001]Mod,_Or_and_Everything
解释
打表可以看出规律。
性质
- 设m=(n-1)/2
- (n mod i ) <= m,且当i <= m时,有 n mod (n-i) = i
结果就是 (n mod i) 取到 0~m 的所有整数,所有数或上就是 2 k − 1 2^k-1 2k−1 。k是m的位数
代码
int tt; cin>>tt;
while(tt --){
int n; cin>>n;
int m = (n-1) / 2,res = 1;
while(m) res <<= 1,m >>= 1;
cout<<(res-1)<<endl;
}
[1005]Minimum_spanning_tree
解释
贪心,以2为根节点,所有质数都与2相连,贡献为 2*质数,其它所有数都与它的因数相连,则贡献为本身。
答案为 ( 3 + n ) ∗ ( n − 2 ) / 2 + ∑ i = 1 c n t p i (3 + n) * (n-2)/2 + \sum_{i=1}^{cnt}p_i (3+n)∗(n−2)/2+∑i=1cntpi, p i p_i pi是大于2的质数 ,预先处理质数。
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e7 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;
int primes[N],cnt=0;
bool vis[N];
void ols(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) primes[++cnt] = i;
for(int j=1;j<=cnt&&primes[j]*i<=n;j++){
vis[primes[j]*i] = true;
if(i % primes[j] == 0) break;
}
}
}
signed main(){
IOS
ols(1e7);
int tt; cin>>tt;
while(tt --){
int n; cin>>n;
int ans = (3 + n) * (n - 2) / 2;
for(int i=2;i<=cnt && primes[i]<=n;i++) ans += primes[i];
cout<<ans<<endl;
}
return 0;
}
[1006]Xorsum
解释
处理异或前缀和,问题将区间转换为求两个点,枚举右端点,每次先询问是否存在最靠右的左端使得异或值大于等于k。之后再向01字典树中插入右端点。
如何询问?
假设处理到第i位,当前数为x
- k的这位为0,则只要x当前位互补的存在,则可以取互补的点为左端点,但是不互补的那位也可以继续往下走。
- k这位为1,只能走x当前为互补的那位。
如何插入?
对当前值进行分解插入到01字典树中,存下最后到当前节点的位置。
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6 + 10;
int son[N][2],last[N],idx = 1;
int a[N];
void insert(int x,int pos){
int p = 1;
for(int i=29;i>=0;i--){
int j = (x >> i) & 1;
if(!son[p][j]) son[p][j] = ++idx;
p = son[p][j];
last[p] = pos;
}
}
int query(int x,int y){
int p = 1,res = -1;
for(int i=29;i>=0;i--){
int jx = (x >> i) & 1;
int jy = (y >> i) & 1;
if(!jy){ // k为0,互补的存在,则一定比k大,先存下res的大小,确定是否取这个
if(son[p][jx^1]) res = max(res,last[son[p][jx^1]]);
p = son[p][jx];
}else p = son[p][jx^1];
if(!p) break;
}
if(p != 0) res = max(res,last[p]);
return res;
}
signed main(){
IOS
int tt; cin>>tt;
while(tt --){
for(int i=1;i<=idx;i++) last[1] = -1,son[i][0] = son[i][1] = 0;
idx = 1;
insert(0,0);
int n,k; cin>>n>>k;
int ansl = 0,ansr = INF;
for(int i=1;i<=n;i++) cin>>a[i],a[i] ^= a[i-1];
for(int i=1;i<=n;i++){
int res = query(a[i],k);
if(res != -1 && (i - res) < (ansr - ansl)) ansr = i,ansl = res;
insert(a[i],i);
}
if(ansr == INF) cout<<-1<<endl;
else cout<<(ansl+1)<<" "<<ansr<<endl;
}
return 0;
}
[1007]Pass!
解释
知识点:推式子,特征方程,bsgs。
步骤
一、 推式子
设 f ( t ) f(t) f(t)为t秒到第一个人的方案数。考虑转移方程。
t − 1 t-1 t−1秒时球一定不在第一个人手上
讨论 t − 2 t-2 t−2秒时球在x手上:
- 假设在第一个人手上,则答案为 f ( t − 2 ) × ( n − 1 ) f(t-2)\times (n-1) f(t−2)×(n−1) 表示下一步传给除第一个人的位置再传到第一个人的方案数。
- 假设不在第一个人手上,假设下一步必然到达第一个位置,则方案数为 f ( t − 1 ) f(t-1) f(t−1),剩下一秒,将这一步插到先前假设的下一步必然到达第一个位置中间,这一步可以到达除当前点,第一个点,则有 f ( t − 1 ) × ( n − 2 ) f(t-1)\times (n-2) f(t−1)×(n−2)
所以 f ( t ) = ( n − 2 ) × f ( t − 1 ) + ( n − 1 ) × f ( t − 2 ) f(t) = (n-2)\times f(t-1) + (n-1)\times f(t-2) f(t)=(n−2)×f(t−1)+(n−1)×f(t−2)
二、特征方程
作用就是将递推式子转换成通项公式。可以参考百度百科,参考资料 。
再结合
f
(
0
)
=
1
,
f
(
1
)
=
0
f(0) = 1,f(1) = 0
f(0)=1,f(1)=0,求出两个解和系数,得到
f
(
t
)
=
(
n
−
1
)
t
+
(
n
−
1
)
×
(
−
1
)
t
n
f(t) = \frac{(n-1)^t+(n-1)\times (-1)^t}{n}
f(t)=n(n−1)t+(n−1)×(−1)t
将上面分奇数偶数考虑
奇数
f
(
t
)
=
(
n
−
1
)
t
−
(
n
−
1
)
n
f(t) = \frac{(n-1)^t-(n-1)}{n}
f(t)=n(n−1)t−(n−1)
偶数
f
(
t
)
=
(
n
−
1
)
t
+
(
n
−
1
)
n
f(t) = \frac{(n-1)^t+(n-1)}{n}
f(t)=n(n−1)t+(n−1)
题目所求为
f
(
t
)
≡
x
m
o
d
(
p
)
f(t) \equiv x\mod(p)
f(t)≡xmod(p)
对偶数变换
f
(
t
)
=
(
n
−
1
)
t
+
(
n
−
1
)
n
⟶
(
n
−
1
)
t
≡
(
n
x
−
(
n
−
1
)
+
p
)
%
p
m
o
d
(
p
)
f(t) = \frac{(n-1)^t+(n-1)}{n} \longrightarrow (n-1)^t \equiv (nx-(n-1)+p)\%p \mod(p)
f(t)=n(n−1)t+(n−1)⟶(n−1)t≡(nx−(n−1)+p)%pmod(p)
经典的求
A
x
≡
B
m
o
d
(
p
)
A^x\equiv B\mod(p)
Ax≡Bmod(p) ,用BSGS就行了
注意,当B为1时要特殊处理。
当对同一个x,t为偶数或者奇数都有解,则取最小。
代码
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
typedef long long ll;
unordered_map<int,int> mp;
int qmi(int a,int b){
int res = 1;
while(b){
if(b & 1) res = (1ll * res * a) % mod;
a = (1ll * a * a) % mod;
b >>= 1;
}
return res;
}
int BSGS(int a,int b,int m){
int s = b;
for(int i=0;i<m;i++){
mp[s] = i;
s = (1ll * s * a) % mod;
}
a = qmi(a,m); s = 1;
for(int i=0;i<=m;i++){
int j = mp.count(s) != 0 ? mp[s] : -1;
if(j != -1 && i*m-j >= 0) return i*m-j;
s = (1ll * s * a) % mod;
}
return -1;
}
signed main(){
int m = sqrt(mod)+1;
int tt; scanf("%d",&tt);
while(tt --){
int n,x; scanf("%d %d",&n,&x);
int b1 = (1ll*n*x+(n-1)) % mod,a = n-1;
if(b1 == 1){
printf("1\n");
continue;
}
mp.clear();
int k1 = BSGS(a,b1,m);
if(k1 == -1 || k1 % 2 == 0) k1 = INF;
int b2 = (1ll*n*x-(n-1)+mod)%mod;
if(b2 == 1){
printf("0\n");
continue;
}
mp.clear();
int k2 = BSGS(a,b2,m);
if(k2 == -1 || k2 % 2 == 1) k2 = INF;
int ans = min(k1,k2);
printf("%d\n",(ans==INF)?-1:ans);
}
return 0;
}
[1008]Maximal_submatrix
解释
法一
将每一行的每一个元素往下递推,存进dp数组里面, d p ( i , j ) dp(i,j) dp(i,j)表示第i行第j列这个元素连续前 d p ( i , j ) dp(i,j) dp(i,j) 行满足非递减。
之后对每一行处理, d p ( i , j ) dp(i,j) dp(i,j)可以看出高,转换成了直方图中最大的矩形,用单调栈维护每一行。
法二
悬线法,转换成01矩阵(当前这个数比上面数大,则转换成1),统计全1矩阵的最大值.
- 滚动数组优化,不然会超内存
- 全一矩阵还应该包含多包含一行,因为全1矩阵的上一行全0也可以加入答案,注意特判当前的高等于行则不加1.
代码
单调栈
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;
stack<int> stk;
int dp[N][N],a[N][N];
int rmi[N],lmi[N];
void slove(int s,int m){
for(int i=1;i<=m;i++) rmi[i] = m+1,lmi[i] = 0;
while(stk.size()) stk.pop();
for(int i=1;i<=m;i++){
while(stk.size() && dp[s][stk.top()] > dp[s][i]) rmi[stk.top()] = i,stk.pop();
stk.push(i);
}
while(stk.size()) stk.pop();
for(int i=m;i>=1;i--){
while(stk.size() && dp[s][stk.top()] > dp[s][i]) lmi[stk.top()] = i,stk.pop();
stk.push(i);
}
}
signed main(){
IOS
int tt; cin>>tt;
while(tt --){
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=m;i++) dp[1][i] = 1;
for(int i=2;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j] >= a[i-1][j]) dp[i][j] = dp[i-1][j] + 1;
else dp[i][j] = 1;
int ans = 0;
for(int i=1;i<=n;i++){
slove(i,m);
for(int j=1;j<=m;j++){
int res = (rmi[j] - lmi[j] - 1) * dp[i][j];
ans = max(ans,res);
}
}
cout<<ans<<endl;
}
return 0;
}
悬线法
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int INF = 0x3f3f3f3f;
int a[N][N],b[N][N];
int up[2][N],l[2][N],r[2][N];
signed main(){
IOS
int tt; cin>>tt;
while(tt --){
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j],b[i][j] = 0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j] >= a[i-1][j]) b[i][j] = 1;
int ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) l[i&1][j] = r[i&1][j] = j,up[i&1][j] = 1;
for(int j=2;j<=m;j++) if(b[i][j] && b[i][j-1]) l[i&1][j] = l[i&1][j-1];
for(int j=m-1;j>=1;j--) if(b[i][j] && b[i][j+1]) r[i&1][j] = r[i&1][j+1];
for(int j=1;j<=m;j++){
if(b[i][j] && b[i-1][j]){
l[i&1][j] = max(l[i&1][j],l[(i-1)&1][j]);
r[i&1][j] = min(r[i&1][j],r[(i-1)&1][j]);
up[i&1][j] = up[(i-1)&1][j] + 1;
}
int maup = (up[i&1][j] == i) ? up[i&1][j] : up[i&1][j] + 1;
ans = max(ans,(r[i&1][j] - l[i&1][j] + 1) * maup);
}
}
cout<<ans<<endl;
}
return 0;
}
[1009]KD-Graph
解释
用并查集维护连通块的数量,从小的边权开始,将两个点对应的集合合并成一个连通块。
有性质
- 答案一定是所有边权值里面的一个数,额外包含0
- 从最小边权开始枚举,一次性把所有相同的边权的点都进行并查集合并,每次有效合并一次则剩余连通块数量减一。
- 某次合并之后集合剩余的连通块数量等于k则为答案,小于k则不存在答案。
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
struct Node{
int x,y,w;
bool operator<(const Node&T)const{
return w < T.w;
}
}edge[N];
int fa[N];
int find(int x){
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
signed main(){
IOS
int tt; cin>>tt;
while(tt --){
int n,m,k; cin>>n>>m>>k;
for(int i=1;i<=n;i++) fa[i] = i;
for(int i=1;i<=m;i++) cin>>edge[i].x>>edge[i].y>>edge[i].w;
sort(edge+1,edge+m+1);
int cnt = n,last = 0,ans = -1;
for(int i=1;i<=m;i++){
if(edge[i].w == last){
int a = find(edge[i].x),b = find(edge[i].y);
fa[a] = b;
if(a != b) cnt --;
}else{
if(cnt < k) break;
else if(cnt == k){
ans = last;
break;
}
last = edge[i].w;
int a = find(edge[i].x),b = find(edge[i].y);
fa[a] = b;
if(a != b) cnt --;
}
}
cout<<ans<<endl;
}
return 0;
}
[1010]zoto
解释
将x,y分开看,在不考虑y轴的情况下,问题就变成了求区间
(
l
,
r
)
(l,r)
(l,r)中不同数的数量。这就是经典的问题,主席树,莫队,树状数组都可以维护这个。再对区间
(
l
,
r
)
(l,r)
(l,r)中不同数进行一个范围限制,主席树和树状数组就有些乏力(其实我没想到)。那就用莫队维护。
如何维护
- 对于x轴的维护,就是板子,主要修改add和remove函数,和增加y轴区间上的询问。
- 用sum[i]表示y方向上第i块里面有多少个不同的数,add和remove对sum进行维护,当维护完之后,对y区间询问。
- y区间询问操作,因为对y轴进行了很多区间划分,完整的块就直接加sum值。对于不完整,直接枚举判断。
- 时间复杂度大致为O( m n m\sqrt{n} mn )
代码
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'|| ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&& ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
int a[N];
int blockx,blocky = 313;
int cnt[N],sum[N],ans[N];
struct Query{
int lx,rx,pos;
int ly,ry;
bool operator <(const Query&T)const{
return lx/blockx == T.lx/blockx ? rx < T.rx : lx < T.lx;
}
}qr[N];
void add(int pos){
if(++cnt[a[pos]] == 1) sum[a[pos]/blocky] ++;
}
void remove(int pos){
if(--cnt[a[pos]] == 0) sum[a[pos]/blocky] --;
}
int query(int pos){
int res = 0,len = pos/blocky;
for(int i=0;i<len;i++) res += sum[i];
for(int i=len*blocky;i<=pos;i++)
res += (cnt[i] >= 1);
return res;
}
signed main(){
int tt; tt = read();
while(tt --){
memset(sum,0,sizeof(sum));
memset(cnt,0,sizeof(cnt));
int n,m; n = read(),m = read();
blockx = sqrt(n);
for(register int i=1;i<=n;i++) a[i] = read();
for(register int i=1;i<=m;i++){
qr[i].lx = read(),qr[i].ly = read(),qr[i].rx = read(),qr[i].ry = read();
qr[i].pos = i;
}
sort(qr+1,qr+m+1);
int curl = qr[1].lx,curr = qr[1].lx-1;
for(register int i=1;i<=m;i++){
int l = qr[i].lx,r = qr[i].rx;
while(curl < l) remove(curl++);
while(curl > l) add(--curl);
while(curr < r) add(++curr);
while(curr > r) remove(curr--);
ans[qr[i].pos] = query(qr[i].ry) - query(qr[i].ly-1);
}
for(register int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}