ICPC网络预选赛2021第2场
ICPC网络预选赛2021第2场
J. Leaking Roof
解题思路:
所有的水都是从高处流向低处,所以我们可以对高度进行排序,从高到低判断处理即可。
时间复杂度\(O(n\times m \times log_2(n\times m))\)
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
#define fi first
#define se second
struct Node
{
int h;
int x,y;
bool operator < (Node t)
{
return h < t.h;
}
};
void solve()
{
int n,m,k;
scanf("%d %d",&n,&k);
m = n;
vector<vector<ll>> h(n + 10,vector<ll>(m + 10));
vector<vector<double>> ans(n + 10,vector<double>(m + 10));
vector<Node> v;
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
scanf("%lld",&h[i][j]);
ans[i][j] += k;
v.push_back({h[i][j],i,j});
}
}
sort(v.begin(),v.end());
auto check = [&](int a,int b,int x,int y)
{
if(a < 1 || a > n || b < 1 || b > m)
{
return false;
}
if(h[x][y] <= h[a][b])
{
return false;
}
return true;
};
for(int i = v.size() - 1;i>=0;i--)
{
int a = v[i].x;
int b = v[i].y;
vector<pair<int,int>> t;
for(int i = 0;i<4;i++)
{
int nx = a + dx[i];
int ny = b + dy[i];
if(check(nx,ny,a,b))
{
t.push_back({nx,ny});
}
}
double d = (double)ans[a][b] / t.size();
for(auto s : t)
{
ans[s.fi][s.se] += d;
}
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
if(h[i][j])
{
printf("0 ");
}
else
{
printf("%.6lf ",ans[i][j]);
}
}
printf("\n");
}
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
M. Addition
解题思路:
我们先按题目所给方式求出\(c = a + b\),然后考虑怎么表示\(c\).
-
将\(c\)按照正常二进制进行拆分。二进制拆分只有唯一的表示方式 \(c = \sum\limits_{i=c_1} ^ {c_n} 2^{i}\)。
-
我们从高位到低位遍历,如果当前第\(i\)位存在贡献,那么说明我们需要\(2^i\)来组成\(c\)。
接下来我们要判断\(sgn_i\):
若\(sgn_i = 1\):说明\(vc_i=1\)刚好能加上\(2^i\)的贡献。
若\(sgn_i = -1\):我们如果\(vc_i = 1\),只能减去\(2^i\)的贡献,并无法达到我们二进制组合的效果。所以,我们要思考等价的操作方法。
不难发现\(2^i = 2^{i+1} - 2^i\),所以,如果\(sgn_{i+1} = 1\),我们可以用\(vc_i= 1,vc_{i+1} +1\)等价替代。
如果\(sgn_{i+1}=-1\)呢:如果\(vc_{i+1}=1\),我们\(vc_i = 1\)不变,让\(vc_{i+1}-1\)同样等价。
如果\(vc_{i+1}=0\),此时我们需要得到\(2^{i+1}\)的贡献,观察上述内容,不难发现,按之前思路继续 像高位寻找等价操作即可。直到找到可等价替代的位置就可以结束跳出了。
时间复杂度(\(O(n^2)\))。
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N =1e5 + 10;
const int mod = 998244353;
int n,m;
void solve()
{
scanf("%d",&n);
vector<int> sg(n + 10),a(n + 10),b(n + 10),c(n + 10);
for(int i = 0;i<n;i++)
{
scanf("%d",&sg[i]);
}
ll sa = 0;
ll sb = 0;
for(int i = 0;i<n;i++)
{
scanf("%d",&a[i]);
sa += sg[i] * a[i] * (1ll << i);
}
for(int i = 0;i<n;i++)
{
scanf("%d",&b[i]);
sb += sg[i] * b[i] * (1ll << i);
}
ll sc = sa + sb;
// cout<<sc<<endl;
// scanf("%d",&sc);
for(int i = n - 1;i>=0;i--)
{
if(sc >> i & 1)
{
c[i] = 1;
}
}
for(int i = n - 1;i>=0;i--)
{
if(c[i])
{
if(sg[i] == -1)
{
for(int j = i + 1;j<n;j++)
{
if(sg[j] == 1)
{
if(c[j] == 1)
{
c[j] = 0;
}
else
{
c[j] = 1;
break;
}
}
else
{
if(c[j])
{
c[j] = 0;
break;
}
else
{
c[j] = 1;
}
}
}
}
}
}
for(int i = 0;i<n;i++)
{
if(i == n - 1)
{
printf("%d\n",c[i]);
}
else
{
printf("%d ",c[i]);
}
}
}
int main()
{
int t = 1;
// cout<<phi[11]<<endl;
// scanf("%d",&t);
while(t--)
{
solve();
}
return 0;
}
L. Euler Function
解题思路:
\(PS:\)以下定理和性质的介绍方式参考董晓算法
欧拉函数的定义:\(\varphi(n)\):1~n中与n互质的数的个数。
欧拉函数的计算公式
由唯一分解定理(不详细):
个人认为根据容斥定理得来的计算过程好理解些:
欧拉函数仅由\(n\)和质因子决定,与质因子的次数无关。
欧拉函数的性质:
- 若\(p\)是质数,则\(\varphi(p) = p - 1\).
- 若\(p\)是质数,则\(\varphi(p^k)=(p-1)p^{k-1}\).
- 积性函数:若\(gcd(a,b)=1\),则\(\varphi(ab) = \varphi(a) \times \varphi(b)\).
个人解题关键:
本题中无论是区间和还是乘法懒标记都是线段树的传统老方,这里就不过多赘述。
处理时的重点在于\(w和a_i\)数据范围很小,我们可以预处理使得求\(w\)的欧拉函数的时间复杂度降为\(O(1)\).
我们将\(w\)进行质数分解,分别乘入区间。对于区间\([l,r]\)判断是否存在该质数,若区间中所有则区间欧拉函数和直接\(\times w\)即可,否则继续向下搜索,直至找到叶子位置\(\times \varphi(w)\)后回溯。
由于\(100\)内质数共有25个,所以每个位置最多搜到底\(25\)次。
时间优化点:
-
在build建树时的\(pushup\)需要让父节点更新包含质数集,我们要遍历左右儿子。时间复杂度\(O(w)\)左右(就是质数的个数)。但是在modify的质数集更新中,由于我们将\(w\)进行了质数拆分,每次最多只是更新当前传来的质数,可以另外写一个\(pushup\)来\(O(1)\)更新。
-
分解质因数:我们正常对一个数分解质因数的时间复杂度为\(O(\sqrt n)\).这里由于我们知道\(a_i和w\)的数据范围,且我们预处理时用筛法求了欧拉函数。那么我们其实可以在预处理时顺便求一下每个数的最小质因子,记为\(minp[n]\)。那么求取\(n\)的质因子可以通过不断地
\[n = \frac n {minp[n]} \]来更新,因为质因子最小为\(2\),这样最多求\(log_2n\)次,即可遍历完所有的质因子。时间复杂度\(O(log_2n)\)。
综上,\((前面的25有点极限,就写上去了)\)时间复杂度\(O(25\times nlog_2n+ nlog_2w + m(log_2w + log_2n))\)。
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N =1e5 + 10;
const int mod = 998244353;
int n,m;
int phi[N];
int cnt;
int primes[N];
bool st[N];
int a[N];
int minp[N];
void init()
{
phi[1]=1;
for(int i=2;i<=110;i++)
{
if(!st[i])
{
primes[cnt++]=i;
phi[i]=i-1;
st[i]=true;
minp[i] = i;
}
for(int j=0;primes[j]<=110/i;j++)
{
st[primes[j]*i]=true;
minp[primes[j] * i] = primes[j];
if(i%primes[j]==0)
{
phi[i*primes[j]]=phi[i]*primes[j];
break;
}
phi[i*primes[j]]=phi[i]*(primes[j]-1);
}
}
}
struct Node
{
int l,r;
ll sum = 0;
ll laz = 1;
bool p[110];
}tr[N<<2];
void pushup(int u)
{
tr[u].sum = (tr[u<<1].sum + tr[u<<1|1].sum) % mod;
for(int i = 1;primes[i]<=100;i++)
{
int j = primes[i];
tr[u].p[j] = tr[u<<1].p[j] && tr[u<<1|1].p[j];
}
}
void eval(int u,int w)
{
tr[u].sum = (tr[u<<1].sum + tr[u<<1|1].sum) % mod;
tr[u].p[w] = tr[u<<1].p[w] && tr[u<<1|1].p[w];
}
void pushdown(int u)
{
if(tr[u].laz > 1)
{
tr[u<<1].laz = (tr[u<<1].laz * tr[u].laz) % mod;
tr[u<<1].sum = (tr[u<<1].sum * tr[u].laz) % mod;
tr[u<<1|1].laz = (tr[u<<1|1].laz * tr[u].laz) % mod;
tr[u<<1|1].sum = (tr[u<<1|1].sum * tr[u].laz) % mod;
}
tr[u].laz = 1;
}
void build(int u,int l,int r)
{
tr[u].l = l;
tr[u].r = r;
if(l == r)
{
int res = a[l];
while(res > 1)
{
tr[u].p[minp[res]] = true;
res /= minp[res];
}
tr[u].sum = phi[a[l]];
tr[u].laz = 1;
return ;
}
int mid = l + r >> 1;
build(u<<1,l,mid);
build(u<<1|1,mid + 1,r);
pushup(u);
}
void modify(int u,int l,int r,int w)
{
if(tr[u].l >= l && tr[u].r <= r && tr[u].p[w])
{
tr[u].sum = (tr[u].sum * w) % mod;
tr[u].laz = (tr[u].laz * w) % mod;
return;
}
else if(tr[u].l == tr[u].r)
{
tr[u].sum = (tr[u].sum * phi[w]) % mod;
tr[u].laz = (tr[u].laz * phi[w]) % mod;
tr[u].p[w] = true;
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid)
{
modify(u<<1,l,r,w);
}
if(r > mid)
{
modify(u<<1|1,l,r,w);
}
eval(u,w);
}
ll query(int u,int l,int r)
{
if(tr[u].l >= l && tr[u].r <= r)
{
return tr[u].sum;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
ll res = 0;
if(l <= mid)
{
res = (res + query(u<<1,l,r)) % mod;
}
if(r > mid)
{
res = (res + query(u<<1|1,l,r)) % mod;
}
return res;
}
void solve()
{
scanf("%d %d",&n,&m);
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build(1,1,n);
while(m--)
{
int t = 0;
scanf("%d",&t);
if(t)
{
int l,r;
scanf("%d %d",&l,&r);
ll res = query(1,l,r);
printf("%lld\n",res);
}
else
{
int l,r,w;
scanf("%d %d %d",&l,&r,&w);
while(w > 1)
{
modify(1,l,r,minp[w]);
w /= minp[w];
}
}
}
}
int main()
{
int t = 1;
init();
// cout<<phi[11]<<endl;
// scanf("%d",&t);
while(t--)
{
solve();
}
return 0;
}
D. Limit
解题思路:
不是万物皆可洛必达,若用洛必达定理进行尝试,我们很快就会发现,样例一解决不了。
因此,尝试从泰勒展开切入。
由于分母相同,我们可以将和式拆分,分别求极限,即单求:
对于\({a_i\cdot ln(1 +b_i\cdot x)}\)我们可进行泰勒展开:
由于本题而言,所给\(t\)是多少,我们求到第几项就行。
正常带入后不难发现:
- 若\(t=0\):那么答案一定为0。
- 若\(t=1\):那么答案为\(\sum_{i=1}^n{a_ib_i}\)。
- 若\(t\geq 2\):如果非最高阶的项中系数不为零,那么答案一定是无穷。反之,最高阶的项中的系数就是答案(记得分数化为最简形式).
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N =1e5 + 10;
const int mod = 998244353;
int n,m;
ll gcd(ll a,ll b)
{
return b ? gcd(b,a%b) : a;
}
void solve()
{
int t;
scanf("%d %d",&n,&t);
vector<ll> a(n + 1);
vector<ll> b(n + 1);
for(int i = 1;i<=n;i++)
{
scanf("%lld %lld",&a[i],&b[i]);
}
if(t == 0)
{
printf("0\n");
}
else if(t == 1)
{
ll sum = 0;
for(int i = 1;i<=n;i++)
{
sum += a[i] * b[i];
}
printf("%lld\n",sum);
}
else
{
ll sum = 0;
for(int i = 1;i<t;i++)
{
sum = 0;
for(int j = 1;j<=n;j++)
{
ll res = 1;
for(int k = 1;k<=i;k++)
{
res *= b[j];
}
sum += a[j] * res;
}
if(sum != 0)
{
puts("infinity");
return;
}
}
sum = 0;
for(int i = 1;i<=n;i++)
{
ll res = 1;
for(int j = 1;j<=t;j++)
{
res *= b[i];
}
sum += a[i] * res;
}
// cout<<sum<<endl;
ll d = gcd(sum,t);
if(!(t & 1))
{
sum = -sum;
}
sum /= d;
t /= d;
if(t == 1)
{
printf("%lld\n",sum);
}
else
{
printf("%lld/%lld\n",sum,t);
}
}
}
int main()
{
int t = 1;
// cout<<phi[11]<<endl;
// scanf("%d",&t);
while(t--)
{
solve();
}
return 0;
}
H. Set
解题思路:
无。不会,看了答案代码也不会。
找不到理解切入点,短时间内感觉难以理解,暂且当经验积累了。
解题代码(原文):
#include<bits/stdc++.h>
using namespace std;
int rand(int l,int r)
{
return (rand() % (r-l+1) + l);
}
void solve()
{
srand(time(0));
int k,r;
scanf("%d %d",&k,&r);
for(int i = 1;i<=k;i++)
{
set<int> s;
while(((int)s.size() ) < (512 + r - 1) / r)
{
s.insert(rand(1,256));
}
for(int j = 1;j<=256;j++)
{
printf("%d",s.count(j));
}
printf("\n");
}
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}