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\).

  1. \(c\)按照正常二进制进行拆分。二进制拆分只有唯一的表示方式 \(c = \sum\limits_{i=c_1} ^ {c_n} 2^{i}\)

  2. 我们从高位到低位遍历,如果当前第\(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互质的数的个数。

欧拉函数的计算公式

由唯一分解定理(不详细):

\[\begin{align} n &= \prod_{i=1}^k p_i^{\alpha_i} = p_1^{\alpha_1}p_2^{\alpha_2}p_3^{\alpha_3}...p_k^{\alpha_k}\\ \varphi(n) &= \prod_{i=1}^k \varphi(p_{i}^{\alpha_i})\\ &= \prod_{i=1}^k p_i^{\alpha_i - 1}(p_i-1)\\ &= ......\\ &= n \times\prod_{i=1}^k(1-\frac 1 p_i) \end{align} \]

个人认为根据容斥定理得来的计算过程好理解些:

\[\begin{align} \varphi(n) &= n - [(\frac n p_1+\frac n p_2 +...+\frac n p_k)-(\frac n {p_1p_2} +\frac n {p_1p_3}+\frac n {p_{k-1}p_{k}})+...+(-1)^{k+1} \times(\frac n {p_1p_2...p_k})]\\ &= n \times(1 - \frac {1} {p_1} -\frac 1 {p_2} - ... -(-1)^{k+1} \times (\frac n {p_1p_2...p_k})) \\ &= n (1 - \frac 1 {p_1})(1 - \frac 1 {p_2})...(1-\frac 1 { p_k})\\ &=n \times\prod_{i=1} ^ k(1 - \frac 1 {p_i}) \end{align} \]

欧拉函数仅由\(n\)和质因子决定,与质因子的次数无关。

欧拉函数的性质:

  1. \(p\)是质数,则\(\varphi(p) = p - 1\).
  2. \(p\)是质数,则\(\varphi(p^k)=(p-1)p^{k-1}\).
  3. 积性函数:若\(gcd(a,b)=1\),则\(\varphi(ab) = \varphi(a) \times \varphi(b)\).

个人解题关键:

\[\begin{align} &设w为质数,则根据欧拉函数计算公式\\ &若 w \mid n,\varphi(n\times w) = \varphi(n) \times w\\ &若 w \nmid n,\varphi(n \times w) = \varphi(n) \times \varphi(w) \end{align} \]

本题中无论是区间和还是乘法懒标记都是线段树的传统老方,这里就不过多赘述。

处理时的重点在于\(w和a_i\)数据范围很小,我们可以预处理使得求\(w\)的欧拉函数的时间复杂度降为\(O(1)\).

我们将\(w\)进行质数分解,分别乘入区间。对于区间\([l,r]\)判断是否存在该质数,若区间中所有则区间欧拉函数和直接\(\times w\)即可,否则继续向下搜索,直至找到叶子位置\(\times \varphi(w)\)后回溯。

由于\(100\)内质数共有25个,所以每个位置最多搜到底\(25\)次。

时间优化点:

  1. 在build建树时的\(pushup\)需要让父节点更新包含质数集,我们要遍历左右儿子。时间复杂度\(O(w)\)左右(就是质数的个数)。但是在modify的质数集更新中,由于我们将\(w\)进行了质数拆分,每次最多只是更新当前传来的质数,可以另外写一个\(pushup\)\(O(1)\)更新。

  2. 分解质因数:我们正常对一个数分解质因数的时间复杂度为\(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

解题思路:

不是万物皆可洛必达,若用洛必达定理进行尝试,我们很快就会发现,样例一解决不了。

因此,尝试从泰勒展开切入。

\[\begin{align} \lim_{x \to 0} {\frac {\sum_{i=1} ^ {n} {a_i\cdot ln(1 +b_i\cdot x)} } {x^t}} \\ \end{align} \]

由于分母相同,我们可以将和式拆分,分别求极限,即单求:

\[\lim_{x \to 0} {\frac {a_i\cdot ln(1 +b_i\cdot x)} {x^t}} \\ \]

对于\({a_i\cdot ln(1 +b_i\cdot x)}\)我们可进行泰勒展开:

\[{a_i\cdot ln(1 +b_i\cdot x)} = a_ib_ix - \frac 1 2(b_ix)^2 + \frac 1 3(b_ix)^3-\frac 1 4(b_ix)^4+\frac 1 5(b_ix)^5+ R_5(x) \]

由于本题而言,所给\(t\)是多少,我们求到第几项就行。

正常带入后不难发现:

  1. \(t=0\):那么答案一定为0。
  2. \(t=1\):那么答案为\(\sum_{i=1}^n{a_ib_i}\)
  3. \(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;
}
posted @ 2023-09-14 21:58  value0  阅读(122)  评论(0)    收藏  举报