2021.11.9 dmy模拟赛
前言
突然发现如果不是写了分层代码,最好还是不要直接按照数据点判掉暴力了,万一常数小多过一个点呢?
期望得分:\(100+30+30+10=170pts\)
实际得分:\(60+30+30+0=120pts\)
什么时候能不挂分...
感觉这次时间分配不错,也可能是因为多了半个点。
首先可喜可贺的是 \(T1\) 控制在了 \(1.5h\) 之内。虽然这期间一开始想了很多无用的结论,导致浪费了时间,但是整体也还可以,大概 \(1h\) 左右推出了可以矩乘的递推式(之前写了一个没法优化的 DP),后来又用 \(30min\) 把矩乘写上了。然而评测之后发现矩阵乘法因为取模挂了(大悲)。
然后开 \(T3\),用了 \(1h\) 左右,推出了很多应该有用的性质,结果写完 DP 发现转移有问题,感觉可以改进,但是比较复杂,所以先放弃了。最后回来写了暴搜,有点遗憾,不过可能本来也做不出来吧。
看 \(T4\),想了半天连样例都推不出来,按照小样例猜了个表上去,希望骗到分。
看 \(T2\),暴力相当好写,可惜进一步就不会了。
检查检查就到时间了。
题解
A poly
矩阵快速幂。注意到 \(x^{n+1}+y^{n+1}=(x+y)(x^n+y^n)-xy(x^{n-1}+y^{n-1})\).
方法和正解一模一样。。。
草,取模的时候虽然考虑到了大部分负数取模 \(+mod\) 的情况,但是忘记了本身的转移矩阵里面有个负数,于是在矩阵乘法里面没有 \(+mod\),痛失 \(40pts!!!\)
贴一下立刻就改完的代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FCC fclose(stdin),fclose(stdout)
const int INF = 0x3f3f3f3f,N = 1e5+10,mod = 998244353;
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
ll n,a,b;
//ll f[N],mi_2[N];
ll dp[N],mi[N];
struct matrix
{
ll mat[3][3];
}base,g,ans;
void init()
{
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
base.mat[i][j]=(i==j);
}
matrix operator * (const matrix &x,const matrix &y)
{
matrix ret;
memset(ret.mat,0,sizeof(ret.mat));
for(int k=1;k<=2;k++)
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
(ret.mat[i][j]+=x.mat[i][k]*y.mat[k][j]+mod)%=mod;
return ret;
}
matrix qpow(matrix x,ll y)
{
matrix ret=base;
while(y)
{
if(y&1) ret=ret*x;
x=x*x;
y>>=1;
}
return ret;
}
void pre()
{
mi[1]=b;
for(ll i=3;i<=n;i++)
{
mi[i]=mi[i-1]*b%mod;
dp[i]=(dp[1]*dp[i-1]%mod-b*dp[i-2]%mod+mod)%mod;
// printf("dp[%lld]=%lld\n",i,dp[i]);
}
printf("%lld\n",dp[n]);
}
int main()
{
// freopen("poly.in","r",stdin);
// freopen("poly.out","w",stdout);
a=read(),b=read(),n=read();
dp[1]=a,dp[2]=(a*a%mod-2*b+mod)%mod;
if(n<=1e5+1) return pre(),0;
init();
g.mat[1][1]=a,g.mat[1][2]=1;
g.mat[2][1]=-b,g.mat[2][2]=0;
ans.mat[1][1]=dp[2],ans.mat[1][2]=dp[1];
ans=ans*qpow(g,n-2);
printf("%lld\n",ans.mat[1][1]);
// FCC;
return 0;
//---------------------------
/*
f[0]=a,mi_2[0]=b;
int lg=log2(n);
for(int i=1;i<=lg;i++)
{
mi_2[i]=mi_2[i-1]*mi_2[i-1]%mod;
f[i]=(f[i-1]*f[i-1]%mod-2*mi_2[i-1]%mod+mod)%mod;
// printf("f[%d]=%lld\n",i,f[i]);
}
for(int i=62;i>=0;i--)
if((1ll<<i)==n) return printf("%lld\n",f[i]),0;
FCC;
*/
return 0;
}
/*
4 3 1024
797892006 888438010 66
*/
B triangle
考虑异色角 \((a, b, c)\) 的对数,满足 \(ab\) 和 \(bc\) 异色。容易发现异色三角形对应两个异色角,同色三角形不对应异色角。计算异色角个数即可。
看了题解真的好简单...也就一点点思维含量而已(而且以前貌似做过类似的题?),转换一下思路,统计出有多少不合法的三角形,过程中记录两种颜色边的数量即可。
问题主要还是每太敢仔细想这道题,没想到实际上并没有那么难,再就是思维有所欠缺。(毕竟不少人都做出来了...)
\(Update:\) 竞赛图三元环计数和这个思想差不多,之前 qyt 出题的时候了解过,但如今看来融会贯通的能力也需要提高。正难则反,补集转化。三元环计数
贴一份代码:
点击查看代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin), freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f, N = 2e5+5, mod = 998244353;
typedef long long ll;
typedef unsigned long long ull;
namespace GenHelper {
unsigned z1,z2,z3,z4,b,u;
unsigned get() {
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1;
return res;
}
void srand(int x) {
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
bool edge[8005][8005];
signed main(){
fo("triangle");
ll n; int seed;
cin >> n >> seed;
srand(seed);
if(n < 3) return puts("0"), 0;
for(int i = 0 ; i < n ; i ++)
for(int j = i + 1 ; j < n ; j ++)
edge[j][i] = edge[i][j] = read();
// for(int i = 1 ; i <= n ; i ++)
// for(int j = 1 ; j <= n ; j ++)
// printf("%d%c",edge[i][j]," \n"[j==n]);
ll ans = n*(n-1)*(n-2)/6, sum = 0;
// printf("ANS = %lld\n",ans);
for(int i = 0 ; i < n ; i ++){
ll cnt1 = 0, cnt2 = 0;
for(int j = 0 ; j < n ; j ++) if(i != j)
edge[i][j] ? cnt1++ : cnt2++;
sum += cnt1 * cnt2;
// printf("%d: [%lld,%lld]\n",i,cnt1,cnt2);
}
printf("%lld",ans-sum/2);
return 0;
}
/*
10
114514
*/
C
容易发现可以通过第一类操作将棋盘染色等价于初解连通。利用prim算法在O(n^2)时间求解最小生成树即可。
本场比赛比较遗憾的题,花了大量时间投入思考,也写出了代码,但由于能力不足没能做出来高分暴力。
或许这个时间用来思考 \(T2\) 更有可能做出来。
不过此题正解确实想不到最小生成树(虽然本来也只想写 \(50-80pts\)),而且最后低保暴力也打完了,勉强不算太亏。
D
考虑记录一个局面。容易发现,可以在每个位置记录一个数 \(a_i\) 表示如果 Alice 最初想的数是 \(i\),那么 Alice 还能说几次谎。
定义 \(dp[i][j][k]\) 表示数由一段 \(i\) 个 \(0\) ,一段 \(j\) 个 \(1\),一段 \(k\) 个 \(0\) 构成时的答案。利用决策单调性可以做到 \(O(n^3)\).
又注意到值域只有 \(O(\log n)\),翻转值域和最后一维后复杂度为 \(O(n^2\log n)\).
首先我们改写游戏。
记a[y]表示:如果A想的数是y,那么A最多还能说a[y]次谎。(a[y] < 0 说明不可能是y了) B获胜当且仅当只有一个a[y]大于等于0.
B每次猜数 A回答之后,所有与A的回答冲突的 y 对应的 a 值减1.
B想要用尽量少的次数将 a 值除一个位置外均变成0 .
对a直接爆搜,期望得分20分。
容易发现,任何时候,非负的a值一定形如:先是 u 个 0,再是 v 个 1,再是 w 个 0. 我们可以用 \(dp[u][v][w]\) 表示这时候还需要猜多少次,然后枚举 B 第一轮的分界位置转移。 这样的复杂度是 \(O(n^4)\). 期望得分40.
但我们注意到,在某个位置之前 Alice 回答大于更优,这个位置之后 Alice 回答小于更优。 我们可以通过记录这一位置将复杂度变为 \(O(n^3)\)。(对于固定u, v,不同w,这一位置是 单调的。因此可以线性算出来。)
期望得分70.
另一方面,注意到dp的值域只有 \(O(logn)\),且关于w单调。我们可以改写dp,写成 \(F[u][v][k]\)表示最大的w, 满足 \(dp[u][v][w] <= k\).
这样的dp状态只有 \(O(n^2 * logn)\) 种,且仍可用类似方法转移。
期望得分100.
这题做不出来感觉问题不大,毕竟博弈论涉及不多。
总结
- 平衡了时间分配,虽然 \(T3\) 翻车了,但是感觉并不是时间分配的锅。
- 思维水平需要提高。
- 最主要的还是不要挂分,写题的时候就应该心里预设一下哪里可能会有坑,而不是样例。