题目
A题意:n个瓶子排成一排,给出瓶子容量a[i],初始瓶子为空。m次操作:每次向x瓶中加入y容量的水,如果瓶子水有多则将多的水倒入下一个瓶子。
问:最终每一个瓶子里的水量。
解法:以前做这题的思路是模拟每一次加水并将多的水倒入下一水瓶。代码写的很烂。
现在重新回顾这题思路就是,先不管瓶子容量直接加水,最后统一将多的水倒入下一瓶子。
const int maxn = 1e5+9;
int a[maxn] , b[maxn];
void solve(){
int n , m ;
scanf("%lld%lld" , &n , &m);
rep(i , 1 , n){
b[i] = 0;
}
rep(i , 1 , n){
scanf("%lld" , &a[i]);
}
rep(i , 1 , m){
int x , y ;
scanf("%lld%lld" , &x , &y);
b[x] += y ;
}
rep(i , 1 , n){
if(b[i] > a[i]){
b[i+1] += b[i] - a[i];
b[i] = a[i] ;
}
}
rep(i , 1 , n-1){
printf("%lld " , b[i]);
}
printf("%lld
" , b[n]);
}
signed main()
{
int t ; scanf("%lld" , &t);while(t--)
//int _ ;cin>>_;while(_--)
solve();
}
B题意:给定一个整数n(4e4),能不能将其分解为三个素数之和,有多少种分法。(2,2,3)与(2,3,2)属于同一种。
解法:打比赛的时候不敢想直接枚举,第一感觉就是必超时。回过头来分析一波,时间复杂度还是可行的。
根据素数定理:1-4e4有大概有5e3个素数,暴力枚举两个素数,判断所剩的第三个数是否素数且递增。时间复杂度((n^2))1e7数量级可接受。
const int maxn = 4e4+9;
int prime[maxn] , len ;
bool is_prime[maxn];
void init(){
ME(is_prime , true);
is_prime[1] = false;
rep(i , 2 , maxn){
if(is_prime[i]){
prime[++len] = i ;
for(int j = i*i ; j <= maxn ; j += i){
is_prime[j] = false;
}
}
}
}
void solve(){
int n , cnt = 0;
scanf("%lld" , &n);
for(int i = 1 ; i <= len && prime[i] < n; i++){
for(int j = i ; j <= len && prime[i] + prime[j] < n && n-prime[i]-prime[j] >= prime[j] ; j++){
int k = n - prime[i] - prime[j] ;
if(is_prime[k] && k >= prime[j]){
cnt++;
}
}
}
cout << cnt << endl;
}
C题意:n(2e5)个坐标点(x,y,z),连接两个点的花费为(MIN(|X_u−X_v|,|Y_u−Y_v|,|Z_u−Z_v|),将n个点连起来最小花费为多少)
解法:稍一分析可以知道是最小生成树问题,难在如何建图,分别以x,y,z排序并以其建图,可得3*(n-1)条边,直接上Kruskal。
const int maxn = 2e5+9;
int fa[maxn];
int len ;
struct node{
int x , y , z , id;
}p[maxn];
struct edge{
int u , v , val;
}e[maxn*3];
void AddEdge(int u , int v , int w){
e[++len].u = u ;
e[len].v = v ;
e[len].val = w ;
}
int cal(int x , int y){return abs(x - y);}
bool cmp(edge a , edge b){return a.val < b.val;}
bool cmp1(node a , node b){return a.x < b.x;}
bool cmp2(node a , node b){return a.y < b.y;}
bool cmp3(node a , node b){return a.z < b.z;}
int find(int x){
if(x != fa[x]){
return fa[x] = find(fa[x]);
}
return fa[x];
}
void unite(int x , int y){
x = find(x) , y = find(y);
fa[x] = y ;
}
void solve(){
int n ;
scanf("%d" , &n);
rep(i , 1 , n) fa[i] = i ;
rep(i , 1 , n){
scanf("%d%d%d" , &p[i].x , &p[i].y , &p[i].z);
p[i].id = i ;
}
sort(p+1 , p+1+n , cmp1);
rep(i , 2 , n){
AddEdge(p[i].id , p[i-1].id , cal(p[i].x , p[i-1].x));
}
sort(p+1 , p+1+n , cmp2);
rep(i , 2 , n){
AddEdge(p[i].id , p[i-1].id , cal(p[i].y , p[i-1].y));
}
sort(p+1 , p+1+n , cmp3);
rep(i , 2 , n){
AddEdge(p[i].id , p[i-1].id , cal(p[i].z , p[i-1].z));
}
ll ans = 0 ;int cnt = 0 ;
sort(e+1 , e+1+len , cmp);
rep(i , 1 , len){
int u , v , w ;
u = e[i].u , v = e[i].v , w = e[i].val;
if(find(fa[u]) != find(fa[v])){
unite(u , v);
ans += w ;
cnt++;
}
if(cnt == n-1) break;
}
printf("%lld
" , ans);
}
D题意:数位dp,以后再补。
E题意:给出一个整数n,将其分解为若干个正整数之和,使得这些正整数的乘积x最大。计算(frac{1}{n}modx)(数据保证gcd(n,x)=1)
就是求n模x下的逆元,首先求出x,分析将x可知尽可能分解多3,可得x最大.
因为x求出来不能保证是质数,所以不能使用费马小定理求逆元,而应该使用扩展欧几里得求解.
int x , y , d ;
int quickpow(int a , int b){
int ans = 1 ;
while(b){
if(b&1){
ans = ans * a ;
}
b >>= 1 ;
a = a * a ;
}
return ans ;
}
void ex_gcd(int a , int b , int &d , int &x , int &y){
if(!b){
d = a ;
x = 1 ;
y = 0;
}else{
ex_gcd(b , a%b , d , x , y);
int t = x ;
x = y ;
y = t - (a/b)*y;
}
}
int inv(int a , int b){
if(gcd(a,b)!=1) return 0;//判断是否存在逆元
ex_gcd(a , b , d , x , y);
int t = b/d;
return (x%t+t)%t;//得最小正整数解
}
void solve(){
int n , x ;
cin >> n ;
if(n % 3 == 0){
x = quickpow(3 , n/3);
}else if(n % 3 == 1){
x = quickpow(3 , n/3-1)*4;
}else{
x = quickpow(3 , n/3)*2;
}
cout << inv(n , x) << endl;
}
F题意:n(1e5)个数,m(1e5)个操作,n个数初始为0,
两种操作区间修改和获取单点值
1、[l,r]区间数翻转0变1,1变0。
2、问下标x数为多少。
解法:直接暴力的话翻转过程时间复杂度O((n))m次操作必超时,所以使用树状数组将区间翻转复杂度降为O(logn),相应得获取某个数得操纵升为O(logn).
int n , m ;
int a[maxn];
int lowerbit(int x){
return x&(-x);
}
void update(int x , int y){
while(x <= n){
a[x] += y ;
x += lowerbit(x);
}
}
int getsum(int x){
int ans = 0;
while(x >= 1){
ans += a[x];
x -= lowerbit(x);
}
return ans ;
}
void solve(){
scanf("%lld%lld" , &n , &m);
while(m--){
int t ;
scanf("%lld" , &t);
if(t == 1){
int l , r ;
scanf("%lld%lld" , &l , &r);
update(l , 1);
update(r+1 , -1);
}else{
int x ;
scanf("%lld" , &x);
printf("%lld
" , getsum(x)%2);
}
}
}
F题意:有n个人,现在有一个聚会,每个人都可以选择参加或者不参加。而参加的人中每个人要么只去送礼物,要么只接受礼物。
不存在全部都接受礼物或者全部都送礼物的情况(这样要么没人送礼物,要么没人接受礼物了)。问有多少中情况?、
解法:手玩几个数据就可得出规律,组合数问题。
const int maxn = 1e5+9;
const int N = 1e6+7;
int fac[maxn] , inv[maxn];
void init(){
fac[0] = inv[1] = inv[0] = 1;
rep(i , 1 , maxn-1){
fac[i] = fac[i-1] * i % mod ;
}
inv[maxn-1] = quickpow(fac[maxn-1] , mod-2);
red(i , maxn-2 , 1){
inv[i] = inv[i+1]%mod * (i+1)%mod;
}
}
int C(int n , int m){
return fac[n] % mod * inv[m] % mod * inv[n-m] % mod ;
}
void solve(){
init();
int n ;
cin >> n ;
int ans = 0 ;
rep(i , 2 , n){
ans = ans % mod + C(n,i)%mod*(quickpow(2,i)-2+mod)%mod;
}
cout << ans%mod << endl;
}
H题意:现在有两个相同大小的地图,左上角为起点,右下角问终点。问是否存在同一条最短路径。最短距离一样,他们走的路径也一样。
解法:三次bfs,第一次对第一个图bfs求出最短路径,第二次对第二个图求出bfs最短路径,如果相等(确保距离相等),再将两个图合并bfs(确保两条最短路径是一样得),比较是否相等。
简化一下对第一个求一次bfs,再求一次合并图得最短路径。
const int maxn = 5e2+9;
char s1[maxn][maxn];
char s2[maxn][maxn];
int n , m ;
int dir[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
int vis[maxn][maxn];
struct node{
int x , y , w ;
};
int bfs(int x , int y , char s[][maxn]){
ME(vis , false);
queue<node>q;
node now = {x , y , 0} , next;
q.push(now);
while(!q.empty()){
now = q.front();q.pop();
if(now.x==n&&now.y==m){
return now.w;
}
rep(i , 0 , 3){
int xx = now.x + dir[i][0];
int yy = now.y + dir[i][1];
int ww = now.w + 1 ;
if(vis[xx][yy] || xx < 1 || xx > n || yy < 1 || yy > m || s[xx][yy] == '#'){
continue;
}
vis[xx][yy] = 1 ;
next.x = xx , next.y = yy , next.w = ww ;
q.push(next);
}
}
return -1 ;
}
void solve(){
scanf("%lld%lld" , &n , &m);
rep(i , 1 , n){
scanf("%s" , s1[i]+1);
}
rep(i , 1 , n){
scanf("%s" , s2[i]+1);
}
rep(i , 1 , n){
rep(j , 1 , m){
if(s1[i][j] == '*' && s2[i][j] == '#'){
s1[i][j] = '#';
}
}
}
int ans1 = bfs(1 , 1 , s2);
int ans2 = bfs(1 , 1 , s1);
if(ans1 == ans2 && ans1 != -1){
cout << "YES" << endl;
}else{
cout << "NO" << endl;
}
}
I题是水题不多说