位运算/构造 B题
题意:
给定一个长度为n(n>=2)的数组a,你可以将a进行不同的排序,
要求排序后结果满足 :
(1 <= i <= n-1)
比如数组长度为5,则要满足
每次问能满足条件的排序方式有多少种。
(种数太多要对1e9+7取模)
解析:
如果满足数组中最小的数>=2,则可以让a1=an=min,在2 - n-1之间随意排序都满足
但是还有一点,如果所有数&起来小于min,则也无解
ll t,n,m;
ll a[N];
int main(){
scanf("%lld",&t);
while(t--){
scanf("%lld",&n);
ll tmp = 1;
ll y = -1;
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);
y &= a[i];
}
for(ll i=1;i<=n-2;i++){
tmp = tmp * i % mod;
}
ll cnt = 0;
for(int i=0;i<n;i++) cnt += (a[i] == y);
ll res = 0;
res = (res + cnt * (cnt - 1) % mod * tmp % mod) % mod;
printf("%lld\n", res);
}
return 0;
}
动态规划 C题
题意:
给定n,m,n代表要修改的数,m代表修改的次数
每次修改,对n的每一位替换成对那位加1后的数,
如 1912 修改一次后变成 21023,1->2 , 9->10 , 1->2 , 2->3
求对n进行m次修改后,最后的n有几位。 (位数太大要对1e9+7取模)
解析:
dp
int t,n,m;
int f[M][10];
int main(){
scanf("%d",&t);
for(int i=0;i<=9;i++) f[0][i] = 1;
for(int i=1;i<M;i++){
for(int j=0;j<9;j++){
f[i][j] = f[i-1][j+1];
}
f[i][9] = (f[i-1][0] + f[i-1][1]) % mod;
}
while(t--){
scanf("%d%d",&n,&m);
ll res = 0;
while(n){
res = (res + f[m][n % 10]) % mod;
n /= 10;
}
printf("%lld\n", res);
}
return 0;
}
最小生成树/并查集 D题
题意:
给定长度为n(n>=2)的数组a,和一个数 p,
考虑建一个顶点为n的无向图,
指定如下规则:
① 如果 ,那么就可以在顶点i 和 j 之间建一条权值为的边
② 如果 i + 1 = j 的话,那么在顶点 i 和 j 之间可以建一条权值为p的边
注意,顶点i和j之间可以建多条边,也就是两条规则可以同时生效,当然,如果两条都不满足,则顶点i和j之间无边。
目标是:输出这个图的最小生成树的总权值。
解析:
按照kruskal算法的思路想。
按照a[i]从小到大排序,
从前往后遍历,如果当前的a[i] 比 p小,那么就 向左遍历一遍,向右遍历一遍,
比如考虑向左遍历的情况,
如果它左边的这个数能整除它,那么这条边就是图里的最短边,直接给 res 加上a[i]
之后再往左走,一旦某个数不能整除它,则之后gcd也都不成立了,直接break
往右边走也一样同理
注意如果某两个点已经被一条最短的边连接起来了,则也直接break,用vis数组存储,之所以可以这样,因为这题是成段的,所以不用并查集也可以做。
遍历的操作因为会标记已加入集合的边,所以是O(n)的
排序复杂度是O(nlogn) 所以总复杂度是O(nlogn)
int t,n,m;
int a[N];
bool vis[N];
int main(){
scanf("%d",&t);
while(t--){
scanf("%d %d",&n,&m);
vector<pii> v;
for(int i=0;i<n;i++) {
scanf("%d",&a[i]);
v.push_back({a[i],i});//pair<权值,在a中的下标>
}
sort(v.begin(),v.end());
ll res = 0;
for(auto t:v){
int value = t.first;
int id = t.second;
if(value >= m) break;//如果当前最小的权值都大于等于m了就直接break
while(id > 0){
if(vis[id-1]) break;//第i-1个点与第i个点之间已有边
if(a[id-1] % value != 0) break;//如果不满足gcd
vis[id - 1] = true;
res += value;
id--;
}
id = t.second;
while(id < n-1){
if(vis[id]) break;//第i个点与第i+1个点之间已有边
if(a[id+1] % value != 0) break;//如果不满足gcd
vis[id] = true;
res += value;
id++;
}
}
for(int i=0;i<n-1;i++){
if(!vis[i]) res += m;//如果还有没有加入集合的,就用m连接起来,res加上m
}
printf("%lld\n", res);
for(int i=0;i<n-1;i++) vis[i] = 0;
}
return 0;
}