前言
突然发现如果不是写了分层代码,最好还是不要直接按照数据点判掉暴力了,万一常数小多过一个点呢?
期望得分:(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
",i,dp[i]);
}
printf("%lld
",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
",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
",i,f[i]);
}
for(int i=62;i>=0;i--)
if((1ll<<i)==n) return printf("%lld
",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],"
"[j==n]);
ll ans = n*(n-1)*(n-2)/6, sum = 0;
// printf("ANS = %lld
",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]
",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^2log 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) 翻车了,但是感觉并不是时间分配的锅。
- 思维水平需要提高。
- 最主要的还是不要挂分,写题的时候就应该心里预设一下哪里可能会有坑,而不是样例。