A. Crossmarket
题意:小红和小蓝走网格,小红从左下角走到右上角,小蓝从左上角走到右下角,小红经过的网格会产生传送门,小蓝可以通过传送门一次行动次数在小红走过的路上任意传送,走一格也需要花一次行动次数,求最少行动次数。
思路:让小红先走完(先向上走,在向右走),此时固定花费n+m-2次行动次数,此时小蓝脚上有传送门,可以传送到同列最底部或者同行最右部,因此肯定传送max(n-1,m-1)的路程最赚(此时花费1行动次数),然后走min(n-1,m-1)次数。
结论:当n=m=1时,输出0,否则输出m+n-2+min(n-1,m-1)+1
代码实现:
- #include
- #define ll long long
- using namespace std;
- signed main()
- {
- ios::sync_with_stdio(0);
- cin.tie(0);
- cout.tie(0);
- int t;
- cin>>t;
- while(t--)
- {
- int n,m;
- cin>>n>>m;
- if(n==1&&m==1)
- cout<<0<<'\n';
- else
- cout<
-2+min(m,n)<<'\n'; - }
- }
B. Beautiful Array
题意:给n,k,b,s,要寻找一个长度为n的数组,每个元素除以k向下取整的和等于b,数组元素的和等于s,如果没有就输出-1。
思路:给定的b,初步假设有b个k,分配到数组的各位置中,若此时数组和小于s,就对数组中元素增加大小,但每个元素增加的量不能超过k-1,因为超过的话k的数量就大于b了,因此一个数组增加的量最大为(k-1)*n,可以将b*ki的值全部放在一个位置,然后所有位置增加大小,将数组和变为s。
代码实现:
- #include
- #define ll long long
- using namespace std;
- const int maxn=1e5+10;
- ll ans[maxn];
- ll vis[maxn];
- signed main()
- {
- ios::sync_with_stdio(0);
- cin.tie(0);
- cout.tie(0);
- int t;
- cin>>t;
- while(t--)
- {
- ll n,k,b,s;
- cin>>n>>k>>b>>s;
- for(int i=1;i<=n;i++)
- ans[i]=0;
- ans[n]=b*k;//全放一起
- ll temp=s-b*k;//此时数组和与s的差距
- if(temp<0)
- {
- cout<<-1<<'\n';
- continue;
- }
- for(int i=1;i<=n&&temp;i++)
- {
- if(temp<=k-1)
- {
- ans[i]+=temp;
- temp=0;
- }
- else
- {
- ans[i]+=k-1;
- temp-=k-1;
- }
- }
- if(temp)//如果差距依旧存在,那么就找不到
- cout<<-1<<'\n';
- else
- {
- for(int i=1;i<=n;i++)
- cout<
' '; - cout<<'\n';
- }
- }
- }
C. Monoblock
题意:给一个数组,有一个函数g(l,r),返回数组[l,r]之间相邻相同元素组成的块的数量,进行m次操作,每次修改一个元素,并且要求输出此时对于所有(l,r)形成的g(l,r)的和。
思路:l,r的区间的个数总共为n*(n+1)/2,一个区间至少有一块,假设一开始数组元素都相同,那么所有区间g的和就为n*(n+1)/2,在此基础上,当出现两个相邻的元素不相同时,那么某些区间的g的值就会加1,考虑每个相邻的不同元素对产生贡献的区间的数量,假设i和i-1元素不同,那么产生贡献的区间的l可以在[1,i-1]中选择,r可以在[i,n]中选择,因此g的和增加(i-1)*(n-i+1),由此计算出未修改前的答案。
进行一次修改,考虑修改前后,该元素和相邻元素的关系,先撤销原来该元素与相邻元素对答案产生的贡献(当原来的元素和其相邻的元素不相同时),在结算修改后与相邻元素对答案的贡献(现在的元素与其相邻的元素不相同时)。
- #include
- #define ll long long
- using namespace std;
- const int maxn=1e5+10;
- ll a[maxn];
- signed main()
- {
- ios::sync_with_stdio(0);
- cin.tie(0);
- cout.tie(0);
- int n,m;
- cin>>n>>m;
- for(int i=1;i<=n;i++)
- cin>>a[i];
- ll ans=0;
- ans+=(ll)(n+1)*n/2;//每个区间的g至少大于1
- for(int i=2;i<=n;i++)
- if(a[i]!=a[i-1])
- ans+=(ll)(i-1)*(n-i+1);//相邻两个不同元素产生的贡献
- while(m--)
- {
- int x,y;
- cin>>x>>y;
- if(x>1&&a[x]!=a[x-1])//撤销该元素与前一个元素的贡献
- ans-=(ll)(x-1)*(n-x+1);
- if(x
1])//撤销该元素与后一个元素的贡献 - ans-=(ll)x*(n-x);
- a[x]=y;
- if(x>1&&a[x]!=a[x-1])//结算该元素与前一个元素的贡献
- ans+=(ll)(x-1)*(n-x+1);
- if(x
1])//结算该元素与后一个元素的贡献 - ans+=(ll)x*(n-x);
- cout<
'\n'; - }
- }
D. 2+ doors
主要思路来源:Codeforces Round #816 (Div. 2) A - D - 知乎 (zhihu.com)
(大佬写的太好了)
题意:n个元素的数组,给q个按位或的等式,要求寻找一个满足条件且字典序最小的数组。
思路:对于a|b=c,考虑c二进制形式上每一位的数字,如果c在第i位的数字为0,那么a和b对应位置必定都为0,如果c在第i位元素为1,那么a与b之间至少有1个数字对应位置要有1,目前不考虑之后的情况,假设只放1个1,为了追求字典序最小,这个1肯定放在后面一个元素上,但是对于一个式子a|b=c,a和b可能都要放1,这考虑起来就非常的麻烦。
将a|b=c对应建一条连接a和b权值为c的无向边,记录其关系,存完图后,对于一个c,0对应的a与b的位置必定为0,而1的位置,先两个都放1,进行双重循环,外循环遍历二进制位数,内循环1-n从前往后遍历。假设当前元素为ai,对于当前位数j是否放1,若此时已经为0了,就跳过,如果此时为1,考虑图中和他相邻的点,并且边的权值的第j位是1(对于j位和ai有关系,两者之间至少要有一个1),这些点的第j位的数字都是1的话,那么ai的第j位的1就不需要了,变为0。
存在a|a=c的情况,此时a=c,不需要再将1改成0。
代码实现
- #include
- #define ll long long
- using namespace std;
- const int maxn=1e5+10;
- struct node{
- int to,v;
- };
- vector
vv[maxn]; - inline void add(int u,int v,int w)
- {
- vv[u].push_back({v,w});
- vv[v].push_back({u,w});
- }
- ll ans[maxn];
- signed main()
- {
- ios::sync_with_stdio(0);
- cin.tie(0);
- cout.tie(0);
- int n,m;
- cin>>n>>m;
- for(int i=1;i<=m;i++)
- {
- int u,v,w;
- cin>>u>>v>>w;
- add(u,v,w);
- }
- for(int i=1;i<=n;i++)//初始都是1
- ans[i]=(1<<30)-1;
- for(int i=1;i<=n;i++)
- {
- for(int j=0;j
size();j++) - {
- int v=vv[i][j].v;
- ans[i]&=v;//a|b=c中,c的0的a和b对应位置也位0,而a和b其他位若不为0,那么就定为1
- }
- }
- for(int j=30;j>=0;j--)
- {
- for(int i=1;i<=n;i++)
- {
- if((1<
- {
- bool f=1;
- for(int k=0;k
size();k++) - {
- int to=vv[i][k].to;
- if(((1<
0||to==i)//如果与之相连的点的对应位置为0或者有自己与自己按位或的式子(此时固定了) - {
- f=0;
- break;
- }
- }
- if(f)
- ans[i]-=(1<
//将j位的1变为0 - }
- }
- }
- for(int i=1;i<=n;i++)
- cout<
' '; - }
E. Long Way Home
题意:n个城市,m条路,可以坐k次飞机,花费(u-v)*(u-v)时间,问从1到1-n所有城市的最短时间。
思路:首先考虑不坐飞机,那么就是简单的最短路问题,计算出每个点的dis后,考虑坐飞机。定义数组dp,用于记录先走一段路(坐飞机或者走路),最后坐飞机(可能不坐)到目的地,概念有点绕,主要是为了转移方程dp[u]=min(dp[u],dis[v]+(u-v)^2)(先到v,最后坐飞机(或者不坐)),然后再将dp的值传给dis,基于当前dis再进行最短路(飞机不一定只有最后坐,跑最短路寻求走路和坐飞机相间的最短时间),此时遇到一个问题,u和v要暴力遍历的话,一个点要遍历n次,那么时间复杂度就会达到n^2.
考虑斜率优化,首先推导公式:
temp1=dis[a]+(u-a)^2 temp2=dis[b]+(u-b)^2
我们要选择时间少的那个temp去和dp[u]作比较
假设temp1>temp2,那么
dis[a]+u^2-2ua+a^2>dis[b]+u^2-2ub+b^2
2u>((dis[b]+b^2)-(dis[a]+a^2))/(b-a)
右边式子形如k=(y2-y1)/(x2-x1)
对于一个点v,那么就将v定义成x,dis[v]+v^2定义成y。
将n个点都定义出对应的x和y画在坐标系上,横坐标x都是从1-n从小到大,对于两个点a,b:
当2u>k时(上面推的),b优于a,反之a优于b。但是此时u是(1-n)中的任意一个数,因此目前不能根据2u判断前后的优劣性。
对于三个点a,b,c形成的两个线段,考虑几种情况:
当前面的斜率k1>k2时,后期根据u结算时,若2u>k1,b优于a,那么2u>k2,此时c优于b,b点没有任何作用,若2u
当k1k1,b优于a,不能确定2u>k2,无法确定b和c优劣性,若2u
因此利用先单调队列,建立斜率逐渐递增的坐标系,此时单调队列的head先不动,因为u不确定。
坐标系建完之后,遍历1-n所有的点,此时u是从小到大排序的,2u也是从小到大,因此对于一个u,可以直接根据其2u删除单调队列的首个元素(若开头两个点形成的斜率小于2u)。对于此时的u,最适合进行状态转移的点就为删除后的首个元素(斜率优化一般都这样)。
概要:求最短路->建立斜率递增的单调队列->根据从小到大的u删除队首元素->根据队首元素进行状态转移
按照以上步骤,进行k次循环
代码实现:
- #include
- #define ll long long
- #define int ll
- #define double long double
- using namespace std;
- const int maxn=1e5+10;
- const int inf=0x3f3f3f3f3f3f3f3f;
- ll dp[maxn],dis[maxn];
- bool vis[maxn];
- int n,m,k;
- struct edge{
- int to,v;
- };
- vector
vv[maxn]; - inline void add(int u,int v,int w)
- {
- vv[u].push_back({v,w});
- vv[v].push_back({u,w});
- }
- struct node{
- int id,dis;
- friend bool operator<(const node&a,const node&b)
- {
- return a.dis>b.dis;
- }
- };
- void dij()//最短路板子
- {
- for(int i=1;i<=n;i++)
- vis[i]=0;
- dis[1]=0;
- priority_queue
q; - for(int i=1;i<=n;i++)//唯一要注意的点,要把所有点放进队列,因为初始的dis不一定是inf
- q.push({i,dis[i]});
- while(!q.empty())
- {
- node p=q.top();
- q.pop();
- int u=p.id;
- if(vis[u])
- continue;
- vis[u]=1;
- for(int i=0;i
size();i++) - {
- int to=vv[u][i].to;
- int w=vv[u][i].v;
- if(dis[to]>dis[u]+w)
- {
- dis[to]=dis[u]+w;
- q.push({to,dis[to]});
- }
- }
- }
- }
- //斜率优化部分
- double x[maxn],y[maxn];//存储x和y
- int que[maxn],head=1,tail=0;//单调队列
- inline double slope(int x1,int x2)//得到斜率
- {
- return (y[x2]-y[x1])/(x[x2]-x[x1]);
- }
- void solve()
- {
- head=1;//初始化单调队列
- tail=0;
- for(int i=1;i<=n;i++)
- {
- x[i]=i;
- y[i]=dis[i]+i*i;
- dp[i]=dis[i];
- }
- //head
- for(int i=1;i<=n;i++)
- {
- while(head
slope(que[tail-1],que[tail])>slope(que[tail],i))//建立斜率单调递增的坐标系 - tail--;
- // 也可以这么写(可以画个图,两个互通的):
- // while(head
slope(que[tail-1],i)) - // tail--;
- que[++tail]=i;
- }
- for(int i=1;i<=n;i++)
- {
- while(head
slope(que[head],que[head+1])<2*i)//根据2i删掉队首元素 - head++;
- int j=que[head];
- dp[i]=min(dp[i],dis[j]+(j-i)*(j-i));//根据队首元素状态转移
- }
- for(int i=1;i<=n;i++)
- dis[i]=dp[i];//记录答案
- }
- signed main()
- {
- ios::sync_with_stdio(0);
- cin.tie(0);
- cout.tie(0);
- cin>>n>>m>>k;
- for(int i=1;i<=m;i++)
- {
- int u,v,w;
- cin>>u>>v>>w;
- add(u,v,w);
- }
- for(int i=1;i<=n;i++)
- dis[i]=dp[i]=inf;
- dp[1]=0;
- dis[1]=0;
- dij();
- for(int i=1;i<=k;i++)
- {
- solve();
- dij();
- }
- for(int i=1;i<=n;i++)
- cout<
' '; - }