2020.11.29集训总结

malkuth

description

称一个矩阵在模𝑝意义下是神奇的,当且仅当它满足下面两个条件:1.该矩阵是一个只含0到𝑝−1的整数的方阵;2.矩阵每行,每列的和在模𝑝意义下相等。给定的𝑛,𝑝,𝑘,请你求出模𝑝意义下n阶神奇的矩阵中每行每列的和均等于𝑘的不同矩阵的个数,两个矩阵不同当且仅当它们在至少某一位上的元素不同。由于答案较大,请你求出答案对 1 0 9 + 9 10^9+9 109+9(一个质数)取模后的结果。

n , p , k ≤ 1 0 9 n,p,k\leq 10^9 n,p,k109

solution

枚举左上方的 ( n − 1 ) × ( n − 1 ) (n-1)\times(n-1) (n1)×(n1) 的矩阵,有 p ( n − 1 ) × ( n − 1 ) p^{(n-1)\times(n-1)} p(n1)×(n1) 种做法,这个时候最右边和最下面的行都确定了,并且 ( n , n ) (n,n) (n,n) 位置上的值是惟一的(因为第 n n n 行前 n − 1 n-1 n1 个和第 n n n 列的前 n − 1 n-1 n1 的和是相等的),所以答案就是 p ( n − 1 ) × ( n − 1 ) p^{(n-1)\times(n-1)} p(n1)×(n1)

O ( log ⁡ p ) \mathcal O(\log p) O(logp)

code

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;
const int mod=1e9+9;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,p,k;

int Qpow(int base,int ind){
	int res=1;
	while(ind){
		if(ind&1)res=1ll*res*base%mod;
		base=1ll*base*base%mod;
		ind>>=1;
	}
	return res;
}

int main() 
{
	freopen("malkuth.in","r",stdin);
	freopen("malkuth.out","w",stdout);
	read(n),read(p),read(k);
	printf("%d\n",Qpow(p,1ll*(n-1)*(n-1)%(mod-1)));
	return 0;	
}
/*
2 5 0

3 3 2

*/

yesod

description

一条从1到𝑛的数轴上有𝑚名快递员。每名快递员有一条送货路线,可以用三个整数𝑡𝑖,𝑐𝑖,𝑝𝑖来描述第𝑖名快递员的送货路线。具体来说,如果𝑐𝑖>0,则第𝑖名快递员会在𝑡𝑖时刻从𝑝𝑖点出发以每秒1单位长度的速度向𝑛号点方向出发前进𝑙𝑖时刻后停下;如果𝑐𝑖<0,则第𝑖名快递员会在𝑡𝑖时刻从𝑝𝑖点出发以每秒1单位长度的速度向1号点方向出发前进𝑐𝑖时刻后停下。你要雇佣这些快递员中的一部分来完成一些运送订单。第𝑖名快递员仅在[𝑡𝑖,𝑡𝑖+|𝑐𝑖|]时刻工作,他可以在这段时间里的任意整数时刻交接快递。而快递员能进行快递的交付当且仅当在某一时刻两名快递员位于同一个整点上。具体来说,𝑖,𝑗号快递员可以在时刻𝑡,位置𝑝进行交接快递,当且仅当:现在你收到了𝑞份订单,其中第𝑖份订单包含两个整数𝑤𝑖,𝑥𝑖,表示这份订单希望你将快递在最晚𝑤𝑖时刻从1号点运送到𝑥𝑖号点,你想算出完成这份订最少需要雇佣多少名快递员。注意着𝑞份订单是相互独立的,所有快递员每次都需要重新雇佣。

对于 80 % 80\% 80% 的数据, n ≤ 2000 , m ≤ 5000 n\leq 2000,m\leq 5000 n2000,m5000
对于全部数据, n ≤ 2000 , m ≤ 1 0 6 , q ≤ 1 0 5 , t i ≤ 2000 n\leq 2000,m\leq 10^6,q\leq 10^5,t_i\leq 2000 n2000,m106q105,ti2000

solution
首先看 80 % 80\% 80% 的数据,我们显然想到离线处理

所以用 f i , j f_{i,j} fi,j 表示走到 i i i,时间为 j j j,最少用几个快递员

显然有转移 f i , j = min ⁡ { f i ′ , j − ∣ i − i ′ ∣ } f_{i,j}=\min\{f_{i^\prime,j-|i-i^\prime|}\} fi,j=min{fi,jii}

但是要注意满足一定的条件,也就是有一个快递员这么走

朴素的转移是 O ( n 2 m ) \mathcal O(n^2m) O(n2m) 的,考虑优化

g i g_i gi 表示第 i i i 个快递员走过的路径上的 f i , j f_{i,j} fi,j 的最小值

按照时间顺序转移就可以做到 O ( n m ) \mathcal O(nm) O(nm)

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2005;
const int M=1e6+5;
const int T=2505;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m,q;
int f[N][T];
int g[M];
vector<int> s[N][T];

struct misaka{
	int t,p,c;	
	bool operator < (const misaka &cmp)const{
		return t<cmp.t;	
	}
}a[M];

int main()
{
	freopen("yesod.in","r",stdin);
	freopen("yesod.out","w",stdout);
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	read(n),read(m);
	Rep(i,1,m)read(a[i].t),read(a[i].p),read(a[i].c);
	sort(a+1,a+m+1);
	Rep(i,1,m)
		if(a[i].c>0)
			Rep(j,a[i].p,a[i].p+a[i].c){
				if(j>n)break;
				if(j-a[i].p+a[i].t>2500)break;
				s[j][j-a[i].p+a[i].t].push_back(i);
			}
		else
			_Rep(j,a[i].p,a[i].p+a[i].c){
				if(j<1)break;
				if(a[i].p-j+a[i].t>2500)break;
				s[j][a[i].p-j+a[i].t].push_back(i);	
			}
	Rep(i,0,2500)f[1][i]=0;
	Rep(j,1,2500)
		Rep(i,1,n){
			for(vector<int>::iterator it=s[i][j].begin();it!=s[i][j].end();it++)f[i][j]=min(f[i][j],g[*it]+1);
			for(vector<int>::iterator it=s[i][j].begin();it!=s[i][j].end();it++)g[*it]=min(g[*it],f[i][j]);
		}
	Rep(i,1,n)
		Rep(j,1,2500)
			f[i][j]=min(f[i][j],f[i][j-1]);
	read(q);
	while(q--){
		int t,x;
		read(t),read(x);
		printf("%d\n",f[x][t]>1e9?-1:f[x][t]);	
	}
	return 0;
}
/*
10 10
2 1 5
5 4 3
8 7 1
9 8 2
3 1 7
6 3 6
9 6 4
6 2 8
6 7 -2
10 8 -8
4
11 10
12 9
13 10
14 10


考虑满分做法,依然是离线

假设只有快递员向右走,那么显然只有 t i − p i t_i-p_i tipi 相同的快递员才有可能遇上,我们可以把它当成一条条时间线

f i , j f_{i,j} fi,j 表示第 i i i 条时间线上走到第 j j j 个点的最少用的快递员数量

显然有 f i , j = min ⁡ { f i , j − t + 1 } , t ≤ l i , j f_{i,j}=\min\{f_{i,j-t}+1\},t\leq l_{i,j} fi,j=min{fi,jt+1},tli,j,其中 l i , j l_{i,j} li,j 是在时间线 i i i 下,从 j j j点往左一个快递员最远能从哪里走过来

如果有向左走的,我们用 r i , j r_{i,j} ri,j 表示在时间线 i i i下,从 j j j 点往右一个快递员最远能从哪里走过来

那么有转移

f i , j = min ⁡ { f i , j − t 1 , f i + t 2 , j + 2 t 2 } , t 1 ≤ l i , j , t 2 ≤ r i , j f_{i,j}=\min\{f_{i,j-t_1},f_{i+t_2,j+2t_2}\},t_1\leq l_{i,j},t_2\leq r_{i,j} fi,j=min{fi,jt1,fi+t2,j+2t2},t1li,j,t2ri,j

发现可以单调队列维护

复杂度 O ( n t ) \mathcal O(nt) O(nt)

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)
# define mp(a,b) make_pair(a,b)
# define fi first
# define se second

typedef long long ll;

const int N=4105;
const int M=4000;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n,m,Q;
int f[N][N];
int l[N][N],r[N][N];
int id[N][N],tot;
pair<int,int> q[N<<1][N>>1];
int head[N<<1],tail[N<<1];

int main()
{
	freopen("yesod.in","r",stdin);
	freopen("yesod.out","w",stdout);
	memset(f,0x3f,sizeof(f));
	read(n),read(m);
	Rep(i,0,M)
		Rep(j,1,n)
			l[i][j]=r[i][j]=j;
	Rep(i,1,m){
		int t,p,c;
		read(t),read(p),read(c);
		t+=2000;
		if(c>0)l[t-p][p+c]=min(l[t-p][p+c],p);
		else r[t-c-p-c][p+c]=max(r[t-c-p-c][p+c],p);
	}
	_Rep(i,M,0)
		_Rep(j,n-1,1){
			l[i][j]=min(l[i][j],l[i][j+1]);
			r[i][j]=max(r[i][j],r[i+2][j-1]);
		}
	Rep(i,0,M){
		head[0]=1,tail[0]=0;
		q[0][++tail[0]]=mp(1,0);
		f[i][1]=0;
		Rep(j,2,n){
			if(i>1)id[i][j]=id[i-2][j+1];
			if(!id[i][j])id[i][j]=++tot,head[tot]=1;
			int tmp=f[i][j];
			while(head[0]<=tail[0]&&q[0][head[0]].fi<l[i][j])head[0]++;
			if(head[0]<=tail[0])tmp=min(tmp,q[0][head[0]].se+1);
			int d=id[i][j];
			while(head[d]<=tail[d]&&q[d][head[d]].fi>r[i][j])head[d]++;
			if(head[d]<=tail[d])tmp=min(tmp,q[d][head[d]].se+1);
			f[i][j]=tmp;
			while(head[0]<=tail[0]&&q[0][tail[0]].se>=f[i][j])tail[0]--;
			q[0][++tail[0]]=mp(j,f[i][j]);
			while(head[d]<=tail[d]&&q[d][tail[d]].se>=f[i][j])tail[d]--;
			q[d][++tail[d]]=mp(j,f[i][j]);
		}
	}
	Rep(i,1,M)
		Rep(j,2,n)
			f[i][j]=min(f[i][j],f[i-1][j]);
	read(Q);
	while(Q--){
		int x,t;
		read(t),read(x);
		int val=f[t-x+2000][x];
		printf("%d\n",val>1e9?-1:val);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值