├── CNAME ├── Procfile ├── db ├── create_db.sql ├── config.js ├── setup.js └── init.sql ├── .env ├── package.json ├── problemSets.html ├── about.html ├── services └── userService.js ├── leaderboard.html ├── server.js ├── userlist.html ├── styles.css ├── auth.js ├── problems.js ├── index.html └── problem.html /CNAME: -------------------------------------------------------------------------------- 1 | etoi.us.kg -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | release: node db/setup.js 3 | -------------------------------------------------------------------------------- /db/create_db.sql: -------------------------------------------------------------------------------- 1 | -- 创建数据库 2 | CREATE DATABASE oj_db; 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://postgres:voxEVCesYbJwoZByqxRIcabXSdizeKHJ@postgres.railway.internal:5432/railway 2 | -------------------------------------------------------------------------------- /db/config.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | require('dotenv').config(); 3 | 4 | const pool = new Pool({ 5 | connectionString: process.env.DATABASE_URL, 6 | ssl: { 7 | rejectUnauthorized: false 8 | } 9 | }); 10 | 11 | module.exports = { 12 | query: (text, params) => pool.query(text, params), 13 | pool 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oj-system", 3 | "version": "1.0.0", 4 | "description": "Online Judge System", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "nodemon server.js", 9 | "setup": "node db/setup.js" 10 | }, 11 | "dependencies": { 12 | "bcrypt": "^5.1.1", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.3.1", 15 | "express": "^4.18.2", 16 | "express-session": "^1.17.3", 17 | "pg": "^8.11.3" 18 | }, 19 | "devDependencies": { 20 | "nodemon": "^3.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /db/setup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { pool } = require('./config'); 4 | 5 | async function setupDatabase() { 6 | try { 7 | // 读取 init.sql 文件内容 8 | const initSql = fs.readFileSync(path.join(__dirname, 'init.sql'), 'utf8'); 9 | 10 | // 连接数据库并执行 SQL 11 | const client = await pool.connect(); 12 | try { 13 | await client.query(initSql); 14 | console.log('数据库表创建成功!'); 15 | } finally { 16 | client.release(); 17 | } 18 | } catch (err) { 19 | console.error('设置数据库时出错:', err); 20 | } finally { 21 | await pool.end(); 22 | } 23 | } 24 | 25 | setupDatabase(); 26 | -------------------------------------------------------------------------------- /db/init.sql: -------------------------------------------------------------------------------- 1 | -- 创建用户表 2 | CREATE TABLE IF NOT EXISTS users ( 3 | id SERIAL PRIMARY KEY, 4 | username VARCHAR(50) UNIQUE NOT NULL, 5 | password VARCHAR(255) NOT NULL, -- 存储加密后的密码 6 | avatar TEXT, -- 存储头像的base64编码 7 | created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, 8 | last_login TIMESTAMP WITH TIME ZONE 9 | ); 10 | 11 | -- 创建已完成题目表 12 | CREATE TABLE IF NOT EXISTS completed_problems ( 13 | id SERIAL PRIMARY KEY, 14 | user_id INTEGER REFERENCES users(id), 15 | problem_id VARCHAR(50) NOT NULL, 16 | completed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, 17 | UNIQUE(user_id, problem_id) 18 | ); 19 | 20 | -- 创建消息表 21 | CREATE TABLE IF NOT EXISTS messages ( 22 | id SERIAL PRIMARY KEY, 23 | from_user_id INTEGER REFERENCES users(id), 24 | to_user_id INTEGER REFERENCES users(id), 25 | message TEXT NOT NULL, 26 | created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, 27 | read BOOLEAN DEFAULT FALSE 28 | ); 29 | -------------------------------------------------------------------------------- /problemSets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 题单 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 18 |
19 | 20 | 49 | 50 | -------------------------------------------------------------------------------- /about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 关于我们 6 | 7 | 8 | 9 | 16 | 17 | 18 |
19 |

关于我们

20 |

欢迎来到我们的在线评测系统!

21 | 22 |

技术栈

23 | 29 | 30 |
31 |

学习路线图

32 |
33 |
34 |

入门阶段

35 |
    36 |
  • 基础语法
  • 37 |
  • 简单算法
  • 38 |
  • 基础数据结构
  • 39 |
40 |
41 |
42 |

进阶阶段

43 |
    44 |
  • 高级算法
  • 45 |
  • 复杂数据结构
  • 46 |
  • 算法设计
  • 47 |
48 |
49 |
50 |

专家阶段

51 |
    52 |
  • 竞赛技巧
  • 53 |
  • 高级优化
  • 54 |
  • 综合应用
  • 55 |
56 |
57 |
58 |
59 | 60 | 63 |
64 | 65 | -------------------------------------------------------------------------------- /services/userService.js: -------------------------------------------------------------------------------- 1 | const db = require('../db/config'); 2 | const bcrypt = require('bcrypt'); 3 | 4 | const userService = { 5 | // 创建新用户 6 | async createUser(username, password, avatar) { 7 | const hashedPassword = await bcrypt.hash(password, 10); 8 | const result = await db.query( 9 | 'INSERT INTO users (username, password, avatar) VALUES ($1, $2, $3) RETURNING id', 10 | [username, hashedPassword, avatar] 11 | ); 12 | return result.rows[0]; 13 | }, 14 | 15 | // 验证用户登录 16 | async validateUser(username, password) { 17 | const result = await db.query('SELECT * FROM users WHERE username = $1', [username]); 18 | if (result.rows.length === 0) { 19 | return null; 20 | } 21 | const user = result.rows[0]; 22 | const validPassword = await bcrypt.compare(password, user.password); 23 | if (!validPassword) { 24 | return null; 25 | } 26 | return user; 27 | }, 28 | 29 | // 更新用户最后登录时间 30 | async updateLastLogin(userId) { 31 | await db.query( 32 | 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = $1', 33 | [userId] 34 | ); 35 | }, 36 | 37 | // 获取用户信息 38 | async getUserByUsername(username) { 39 | const result = await db.query('SELECT * FROM users WHERE username = $1', [username]); 40 | return result.rows[0]; 41 | }, 42 | 43 | // 标记题目为已完成 44 | async markProblemAsCompleted(userId, problemId) { 45 | try { 46 | await db.query( 47 | 'INSERT INTO completed_problems (user_id, problem_id) VALUES ($1, $2)', 48 | [userId, problemId] 49 | ); 50 | return true; 51 | } catch (error) { 52 | if (error.code === '23505') { // 唯一约束违反 53 | return false; 54 | } 55 | throw error; 56 | } 57 | }, 58 | 59 | // 获取用户已完成的题目 60 | async getCompletedProblems(userId) { 61 | const result = await db.query( 62 | 'SELECT problem_id FROM completed_problems WHERE user_id = $1', 63 | [userId] 64 | ); 65 | return result.rows.map(row => row.problem_id); 66 | } 67 | }; 68 | 69 | module.exports = userService; 70 | -------------------------------------------------------------------------------- /leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 排行榜 6 | 7 | 8 | 9 | 10 |
11 |

排行榜

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
用户名完成题目数操作
24 | 返回首页 25 |
26 | 27 | 65 | 66 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const session = require('express-session'); 4 | const userService = require('./services/userService'); 5 | 6 | const app = express(); 7 | const port = 3000; 8 | 9 | app.use(cors({ 10 | origin: 'http://localhost:5500', 11 | credentials: true 12 | })); 13 | 14 | app.use(express.json({ limit: '50mb' })); 15 | app.use(session({ 16 | secret: 'your-secret-key', 17 | resave: false, 18 | saveUninitialized: false, 19 | cookie: { 20 | secure: false, // 开发环境下设为false 21 | maxAge: 7 * 24 * 60 * 60 * 1000 // 7天 22 | } 23 | })); 24 | 25 | // 注册 26 | app.post('/api/register', async (req, res) => { 27 | try { 28 | const { username, password, avatar } = req.body; 29 | 30 | // 检查用户名是否已存在 31 | const existingUser = await userService.getUserByUsername(username); 32 | if (existingUser) { 33 | return res.status(400).json({ error: '用户名已存在' }); 34 | } 35 | 36 | const user = await userService.createUser(username, password, avatar); 37 | res.json({ success: true, userId: user.id }); 38 | } catch (error) { 39 | console.error('注册错误:', error); 40 | res.status(500).json({ error: '注册失败' }); 41 | } 42 | }); 43 | 44 | // 登录 45 | app.post('/api/login', async (req, res) => { 46 | try { 47 | const { username, password } = req.body; 48 | const user = await userService.validateUser(username, password); 49 | 50 | if (!user) { 51 | return res.status(401).json({ error: '用户名或密码错误' }); 52 | } 53 | 54 | await userService.updateLastLogin(user.id); 55 | req.session.userId = user.id; 56 | req.session.username = user.username; 57 | 58 | res.json({ 59 | success: true, 60 | user: { 61 | id: user.id, 62 | username: user.username, 63 | avatar: user.avatar 64 | } 65 | }); 66 | } catch (error) { 67 | console.error('登录错误:', error); 68 | res.status(500).json({ error: '登录失败' }); 69 | } 70 | }); 71 | 72 | // 获取用户信息 73 | app.get('/api/user', async (req, res) => { 74 | if (!req.session.userId) { 75 | return res.status(401).json({ error: '未登录' }); 76 | } 77 | 78 | try { 79 | const user = await userService.getUserByUsername(req.session.username); 80 | const completedProblems = await userService.getCompletedProblems(user.id); 81 | 82 | res.json({ 83 | id: user.id, 84 | username: user.username, 85 | avatar: user.avatar, 86 | completedProblems 87 | }); 88 | } catch (error) { 89 | console.error('获取用户信息错误:', error); 90 | res.status(500).json({ error: '获取用户信息失败' }); 91 | } 92 | }); 93 | 94 | // 标记题目完成 95 | app.post('/api/problems/complete', async (req, res) => { 96 | if (!req.session.userId) { 97 | return res.status(401).json({ error: '未登录' }); 98 | } 99 | 100 | try { 101 | const { problemId } = req.body; 102 | const success = await userService.markProblemAsCompleted(req.session.userId, problemId); 103 | res.json({ success }); 104 | } catch (error) { 105 | console.error('标记题目完成错误:', error); 106 | res.status(500).json({ error: '操作失败' }); 107 | } 108 | }); 109 | 110 | // 退出登录 111 | app.post('/api/logout', (req, res) => { 112 | req.session.destroy(); 113 | res.json({ success: true }); 114 | }); 115 | 116 | app.listen(port, () => { 117 | console.log(`服务器运行在 http://localhost:${port}`); 118 | }); 119 | -------------------------------------------------------------------------------- /userlist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用户列表 6 | 7 | 8 | 9 | 10 |
11 |

用户列表

12 |
13 | 14 |
15 |
16 |

所有用户

17 | 20 |
21 |
22 |

我的收件箱

23 | 26 |
27 | 返回首页 28 |
29 | 30 | 228 | 229 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Arial, sans-serif; 4 | background: linear-gradient(135deg, #E3F2FD, #BBDEFB); 5 | background-size: 200% 200%; 6 | animation: gradientAnimation 10s ease infinite; 7 | position: relative; 8 | overflow-x: hidden; 9 | } 10 | 11 | body::before, 12 | body::after { 13 | content: "🌟"; 14 | position: fixed; 15 | font-size: 24px; 16 | transition: all 0.5s ease; 17 | opacity: 0.7; 18 | z-index: -1; 19 | } 20 | 21 | body::before { 22 | top: 20px; 23 | left: 20px; 24 | animation: floatTopLeft 3s ease-in-out infinite; 25 | } 26 | 27 | body::after { 28 | bottom: 20px; 29 | right: 20px; 30 | animation: floatBottomRight 3s ease-in-out infinite; 31 | } 32 | 33 | @keyframes gradientAnimation { 34 | 0% { background-position: 0% 50%; } 35 | 50% { background-position: 100% 50%; } 36 | 100% { background-position: 0% 50%; } 37 | } 38 | 39 | @keyframes floatTopLeft { 40 | 0% { transform: translate(0, 0) rotate(0deg); } 41 | 50% { transform: translate(10px, 10px) rotate(180deg); } 42 | 100% { transform: translate(0, 0) rotate(360deg); } 43 | } 44 | 45 | @keyframes floatBottomRight { 46 | 0% { transform: translate(0, 0) rotate(0deg); } 47 | 50% { transform: translate(-10px, -10px) rotate(-180deg); } 48 | 100% { transform: translate(0, 0) rotate(-360deg); } 49 | } 50 | 51 | .container { 52 | max-width: 800px; 53 | margin: 60px auto 0; 54 | padding: 20px; 55 | background-color: rgba(255, 255, 255, 0.95); 56 | border-radius: 8px; 57 | box-shadow: 0 4px 12px rgba(33, 150, 243, 0.15); 58 | position: relative; 59 | z-index: 1; 60 | } 61 | 62 | .container::before, 63 | .container::after { 64 | display: none; 65 | } 66 | 67 | .details { 68 | margin-top: 20px; 69 | } 70 | h5 { 71 | margin: 10px 0; 72 | color: #007bff; 73 | font-weight: bold; 74 | transition: color 0.3s; 75 | } 76 | h5:hover { 77 | color: #0056b3; 78 | } 79 | p, small, pre { 80 | margin: 5px 0; 81 | color: #555; 82 | } 83 | pre { 84 | background-color: #f1f1f1; 85 | padding: 10px; 86 | border-radius: 4px; 87 | overflow-x: auto; 88 | } 89 | button { 90 | padding: 10px 20px; 91 | background-color: #2196F3; 92 | color: white; 93 | border: none; 94 | border-radius: 4px; 95 | cursor: pointer; 96 | transition: background-color 0.3s; 97 | } 98 | button:hover { 99 | background-color: #1976D2; 100 | } 101 | button.active { 102 | background-color: #1565C0; 103 | } 104 | a { 105 | display: inline-block; 106 | margin-top: 20px; 107 | color: #007bff; 108 | text-decoration: none; 109 | transition: color 0.3s; 110 | } 111 | a:hover { 112 | text-decoration: underline; 113 | color: #0056b3; 114 | } 115 | small { 116 | display: block; 117 | margin: 5px 0; 118 | color: #555; 119 | } 120 | .difficulty-easy { 121 | color: #4CAF50; 122 | } 123 | .difficulty-medium { 124 | color: #FF9800; 125 | } 126 | .difficulty-hard { 127 | color: #F44336; 128 | } 129 | .tag { 130 | display: inline-block; 131 | background-color: #e0e0e0; 132 | border-radius: 4px; 133 | padding: 2px 8px; 134 | margin: 2px; 135 | font-size: 0.9em; 136 | } 137 | #pagination { 138 | margin-top: 20px; 139 | text-align: center; 140 | } 141 | .problem { 142 | padding: 10px; 143 | border-bottom: 1px solid #ddd; 144 | } 145 | .about-button { 146 | color: #2196F3; 147 | text-decoration: none; 148 | transition: color 0.3s; 149 | margin-left: 10px; 150 | } 151 | .about-button:hover { 152 | color: #1976D2; 153 | } 154 | .footer { 155 | text-align: center; 156 | margin-top: 40px; 157 | padding-bottom: 20px; 158 | } 159 | input[type="text"], select { 160 | width: calc(100% - 22px); 161 | padding: 10px; 162 | margin-bottom: 10px; 163 | border: 1px solid #ccc; 164 | border-radius: 4px; 165 | box-sizing: border-box; 166 | } 167 | #loading { 168 | text-align: center; 169 | font-size: 16px; 170 | color: #666; 171 | margin-top: 20px; 172 | } 173 | .github-link { 174 | color: #333; 175 | text-decoration: none; 176 | margin-left: 10px; 177 | transition: color 0.3s; 178 | } 179 | .github-link:hover { 180 | color: #007bff; 181 | } 182 | #auth, #loginForm, #registerForm { 183 | margin-bottom: 20px; 184 | text-align: center; 185 | } 186 | 187 | #loginForm input, #registerForm input { 188 | display: block; 189 | margin: 10px auto; 190 | padding: 10px; 191 | width: 80%; 192 | max-width: 300px; 193 | border: 1px solid #ccc; 194 | border-radius: 4px; 195 | } 196 | 197 | #loginForm button, #registerForm button { 198 | margin-top: 10px; 199 | } 200 | 201 | #registerForm input[type="file"] { 202 | margin: 10px auto; 203 | display: block; 204 | } 205 | 206 | img { 207 | display: block; 208 | margin: 20px auto; 209 | width: 100px; 210 | height: 100px; 211 | border-radius: 50%; 212 | border: 2px solid #ccc; 213 | } 214 | 215 | .avatar-container { 216 | position: absolute; 217 | top: 10px; 218 | right: 20px; 219 | display: flex; 220 | align-items: center; 221 | z-index: 3; 222 | margin-top: 50px; 223 | } 224 | 225 | .user-avatar { 226 | width: 50px; 227 | height: 50px; 228 | border-radius: 50%; 229 | border: 2px solid #ccc; 230 | cursor: pointer; 231 | transition: all 0.3s ease; 232 | } 233 | 234 | .user-avatar:hover { 235 | transform: scale(1.1); 236 | border-color: #2196F3; 237 | box-shadow: 0 0 10px rgba(33, 150, 243, 0.3); 238 | } 239 | 240 | .avatar-container::after { 241 | content: "点击进入用户中心"; 242 | position: absolute; 243 | bottom: -20px; 244 | right: 0; 245 | font-size: 12px; 246 | color: #666; 247 | opacity: 0; 248 | transition: opacity 0.3s ease; 249 | white-space: nowrap; 250 | } 251 | 252 | .avatar-container:hover::after { 253 | opacity: 1; 254 | } 255 | 256 | .modal { 257 | display: none; 258 | position: fixed; 259 | z-index: 1000; 260 | left: 0; 261 | top: 0; 262 | width: 100%; 263 | height: 100%; 264 | background-color: rgba(0, 0, 0, 0.5); 265 | padding: 0; 266 | box-sizing: border-box; 267 | overflow-y: auto; 268 | } 269 | 270 | .modal-content { 271 | background-color: #fefefe; 272 | margin: 5% auto; 273 | padding: 20px; 274 | border: 1px solid #888; 275 | width: 80%; 276 | max-width: 400px; 277 | border-radius: 8px; 278 | } 279 | 280 | .close { 281 | color: #aaa; 282 | float: right; 283 | font-size: 28px; 284 | font-weight: bold; 285 | cursor: pointer; 286 | margin-left: 15px; 287 | } 288 | 289 | .close:hover, 290 | .close:focus { 291 | color: black; 292 | text-decoration: none; 293 | cursor: pointer; 294 | } 295 | 296 | h1, h2 { 297 | text-align: center; 298 | color: #333; 299 | } 300 | 301 | ul { 302 | list-style-type: none; 303 | padding: 0; 304 | } 305 | 306 | li { 307 | margin: 10px 0; 308 | padding: 10px; 309 | background-color: #f1f1f1; 310 | border-radius: 4px; 311 | } 312 | 313 | .footer { 314 | text-align: center; 315 | margin-top: 20px; 316 | } 317 | 318 | table { 319 | width: 100%; 320 | border-collapse: collapse; 321 | margin-top: 20px; 322 | } 323 | 324 | th, td { 325 | border: 1px solid #ddd; 326 | padding: 8px; 327 | text-align: center; 328 | } 329 | 330 | th { 331 | background-color: #f2f2f2; 332 | font-weight: bold; 333 | } 334 | 335 | .action-button { 336 | background-color: #f0f0f0; 337 | border: none; 338 | border-radius: 4px; 339 | padding: 5px 10px; 340 | margin: 0 5px; 341 | cursor: pointer; 342 | transition: background-color 0.3s; 343 | } 344 | 345 | .action-button:hover { 346 | background-color: #e0e0e0; 347 | } 348 | 349 | .action-button i { 350 | margin-right: 5px; 351 | } 352 | .user-list, .inbox { 353 | margin-top: 20px; 354 | } 355 | 356 | .user-avatar-small { 357 | width: 30px; 358 | height: 30px; 359 | border-radius: 50%; 360 | margin-right: 10px; 361 | vertical-align: middle; 362 | } 363 | 364 | #userList li, #inboxList li { 365 | display: flex; 366 | align-items: center; 367 | justify-content: space-between; 368 | margin: 10px 0; 369 | padding: 10px; 370 | background-color: #f1f1f1; 371 | border-radius: 4px; 372 | } 373 | 374 | #userList button { 375 | margin-left: 10px; 376 | padding: 5px 10px; 377 | background-color: #4CAF50; 378 | color: white; 379 | border: none; 380 | border-radius: 4px; 381 | cursor: pointer; 382 | transition: background-color 0.3s; 383 | } 384 | 385 | #userList button:hover { 386 | background-color: #45a049; 387 | } 388 | .header { 389 | position: absolute; 390 | top: 10px; 391 | left: 10px; 392 | width: 100%; 393 | display: flex; 394 | justify-content: space-between; 395 | align-items: center; 396 | padding: 0 20px 0 0; 397 | box-sizing: border-box; 398 | z-index: 2; 399 | } 400 | 401 | .nav-links { 402 | display: flex; 403 | gap: 20px; 404 | margin-right: 20px; 405 | } 406 | 407 | .nav-button { 408 | display: flex; 409 | align-items: center; 410 | gap: 5px; 411 | padding: 8px 16px; 412 | background-color: #2196F3; 413 | color: white; 414 | border-radius: 4px; 415 | text-decoration: none; 416 | transition: all 0.3s ease; 417 | margin: 0; 418 | } 419 | 420 | .nav-button:hover { 421 | background-color: #1976D2; 422 | transform: translateY(-2px); 423 | text-decoration: none; 424 | } 425 | 426 | .nav-button i { 427 | font-size: 16px; 428 | } 429 | 430 | .mail-icon { 431 | color: #333; 432 | text-decoration: none; 433 | font-size: 24px; 434 | transition: color 0.3s; 435 | position: relative; 436 | z-index: 3; 437 | } 438 | 439 | .mail-icon:hover { 440 | color: #007bff; 441 | } 442 | 443 | .user-profile { 444 | max-width: 600px; 445 | margin: 40px auto; 446 | padding: 20px; 447 | background: white; 448 | border-radius: 8px; 449 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 450 | } 451 | 452 | .back-button { 453 | display: inline-flex; 454 | align-items: center; 455 | gap: 8px; 456 | color: #333; 457 | text-decoration: none; 458 | padding: 8px 16px; 459 | border-radius: 4px; 460 | transition: background-color 0.3s; 461 | } 462 | 463 | .back-button:hover { 464 | background-color: #f0f0f0; 465 | } 466 | 467 | .user-info { 468 | display: flex; 469 | align-items: center; 470 | gap: 10px; 471 | } 472 | 473 | .user-info span { 474 | color: #333; 475 | font-size: 14px; 476 | } 477 | 478 | .top-nav { 479 | position: fixed; 480 | top: 0; 481 | left: 0; 482 | right: 0; 483 | height: 60px; 484 | background-color: white; 485 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 486 | z-index: 1000; 487 | } 488 | 489 | .top-buttons { 490 | max-width: 1200px; 491 | margin: 0 auto; 492 | height: 100%; 493 | display: flex; 494 | justify-content: space-between; 495 | align-items: center; 496 | padding: 0 20px; 497 | } 498 | 499 | .skip-login { 500 | position: absolute; 501 | top: 20px; 502 | right: 20px; 503 | } 504 | 505 | .skip-login a { 506 | color: #666; 507 | text-decoration: none; 508 | font-size: 14px; 509 | transition: color 0.3s; 510 | } 511 | 512 | .skip-login a:hover { 513 | color: #2196F3; 514 | } 515 | 516 | .auth-buttons { 517 | position: absolute; 518 | top: 20px; 519 | right: 20px; 520 | display: flex; 521 | gap: 10px; 522 | z-index: 2; 523 | } 524 | 525 | .auth-btn { 526 | padding: 8px 16px; 527 | background-color: #2196F3; 528 | color: white; 529 | border: none; 530 | border-radius: 4px; 531 | cursor: pointer; 532 | font-size: 14px; 533 | transition: all 0.3s ease; 534 | } 535 | 536 | .auth-btn:hover { 537 | background-color: #1976D2; 538 | transform: translateY(-1px); 539 | } 540 | 541 | .header { 542 | display: flex; 543 | justify-content: space-between; 544 | align-items: center; 545 | margin-bottom: 30px; 546 | } -------------------------------------------------------------------------------- /auth.js: -------------------------------------------------------------------------------- 1 | // API 基础URL 2 | const API_BASE_URL = 'http://localhost:3000/api'; 3 | 4 | function showLogin() { 5 | document.getElementById('auth').style.display = 'none'; 6 | document.getElementById('loginForm').style.display = 'block'; 7 | document.getElementById('registerForm').style.display = 'none'; 8 | } 9 | 10 | function showRegister() { 11 | document.getElementById('auth').style.display = 'none'; 12 | document.getElementById('registerForm').style.display = 'block'; 13 | document.getElementById('loginForm').style.display = 'none'; 14 | } 15 | 16 | async function register() { 17 | const username = document.getElementById('registerUsername').value; 18 | const password = document.getElementById('registerPassword').value; 19 | const avatar = document.getElementById('registerAvatar').files[0]; 20 | 21 | if (!username || !password || !avatar) { 22 | alert('请输入用户名、密码并上传头像。'); 23 | return; 24 | } 25 | 26 | try { 27 | // 将头像转换为base64 28 | const base64Avatar = await new Promise((resolve, reject) => { 29 | const reader = new FileReader(); 30 | reader.onload = e => resolve(e.target.result); 31 | reader.onerror = reject; 32 | reader.readAsDataURL(avatar); 33 | }); 34 | 35 | const response = await fetch(`${API_BASE_URL}/register`, { 36 | method: 'POST', 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | }, 40 | body: JSON.stringify({ 41 | username, 42 | password, 43 | avatar: base64Avatar 44 | }), 45 | credentials: 'include' 46 | }); 47 | 48 | const data = await response.json(); 49 | if (response.ok) { 50 | alert('注册成功!'); 51 | showLogin(); 52 | } else { 53 | alert(data.error || '注册失败'); 54 | } 55 | } catch (error) { 56 | console.error('注册错误:', error); 57 | alert('注册失败,请重试'); 58 | } 59 | } 60 | 61 | async function login() { 62 | clearErrors(); 63 | const username = document.getElementById('loginUsername').value; 64 | const password = document.getElementById('loginPassword').value; 65 | 66 | if (!username || !password) { 67 | !username && showError('loginUsernameError', '请输入用户名'); 68 | !password && showError('loginPasswordError', '请输入密码'); 69 | return; 70 | } 71 | 72 | try { 73 | const response = await fetch(`${API_BASE_URL}/login`, { 74 | method: 'POST', 75 | headers: { 76 | 'Content-Type': 'application/json', 77 | }, 78 | body: JSON.stringify({ username, password }), 79 | credentials: 'include' 80 | }); 81 | 82 | const data = await response.json(); 83 | if (response.ok) { 84 | window.location.href = 'index.html'; 85 | } else { 86 | showError('loginPasswordError', data.error || '登录失败'); 87 | } 88 | } catch (error) { 89 | console.error('登录错误:', error); 90 | showError('loginPasswordError', '登录失败,请重试'); 91 | } 92 | } 93 | 94 | // 添加等级计算函数 95 | function calculateLevel(completedCount) { 96 | if (completedCount >= 50) return { level: "大师", color: "#ff4757", progress: 100 }; 97 | if (completedCount >= 30) return { level: "专家", color: "#ffa502", progress: 80 }; 98 | if (completedCount >= 20) return { level: "高手", color: "#2ed573", progress: 60 }; 99 | if (completedCount >= 10) return { level: "进阶", color: "#1e90ff", progress: 40 }; 100 | if (completedCount >= 5) return { level: "新手", color: "#a4b0be", progress: 20 }; 101 | return { level: "初学者", color: "#747d8c", progress: 0 }; 102 | } 103 | 104 | // 修改 openUserCenter 函数 105 | function openUserCenter() { 106 | const username = localStorage.getItem('currentUser'); 107 | if (!username) return; 108 | 109 | const userData = JSON.parse(localStorage.getItem(username)); 110 | document.getElementById('userCenter').style.display = 'block'; 111 | 112 | // 计算成就和等级 113 | const completedCount = userData.completedProblems ? userData.completedProblems.length : 0; 114 | const levelInfo = calculateLevel(completedCount); 115 | const nextLevel = getNextLevel(completedCount); 116 | 117 | // 更新用户中心内容 118 | document.querySelector('.modal-content').innerHTML = ` 119 | × 120 |
121 |
122 | ${(() => { 123 | const title = calculateTitle(userData); 124 | return ` 125 | 126 | ${title.icon} ${title.name} 127 | 128 | `; 129 | })()} 130 |
131 |
132 | 当前头像 133 |
134 | 137 | 138 |
139 |
140 |
141 |

用户名: ${username}

142 |
143 | 144 | 145 | 148 |
149 |
150 |
151 | 完成题目数: ${completedCount} 152 |
153 |
154 |

修改密码

155 | 156 | 157 |
158 |
159 | 162 |
163 |
164 | `; 165 | } 166 | 167 | function closeUserCenter() { 168 | document.getElementById('userCenter').style.display = 'none'; 169 | } 170 | 171 | function changePassword() { 172 | const newPassword = document.getElementById('newPassword').value; 173 | const username = localStorage.getItem('currentUser'); 174 | const userData = JSON.parse(localStorage.getItem(username)); 175 | 176 | if (newPassword) { 177 | userData.password = newPassword; 178 | localStorage.setItem(username, JSON.stringify(userData)); 179 | alert('密码修改成功!'); 180 | closeUserCenter(); 181 | } else { 182 | alert('请输入新密码。'); 183 | } 184 | } 185 | 186 | function markAsDone(problemId) { 187 | const username = localStorage.getItem('currentUser'); 188 | if (!username) { 189 | alert('请先登录!'); 190 | return; 191 | } 192 | 193 | const userData = JSON.parse(localStorage.getItem(username)); 194 | if (!userData.completedProblems) { 195 | userData.completedProblems = []; 196 | } 197 | 198 | if (!userData.completedProblems.includes(problemId)) { 199 | userData.completedProblems.push(problemId); 200 | localStorage.setItem(username, JSON.stringify(userData)); 201 | alert('题目已标记为完成!'); 202 | } else { 203 | const problemTitle = problems.find(p => p.id === problemId)?.title || problemId; 204 | alert(`您已经完成过 "${problemTitle}" 这道题目了!`); 205 | } 206 | 207 | updateProgress(); 208 | updateDailyStats(); 209 | } 210 | 211 | // 获取下一个等级信息 212 | function getNextLevel(completedCount) { 213 | if (completedCount >= 50) return null; 214 | if (completedCount >= 30) return { level: "大师", required: 50 }; 215 | if (completedCount >= 20) return { level: "专家", required: 30 }; 216 | if (completedCount >= 10) return { level: "高手", required: 20 }; 217 | if (completedCount >= 5) return { level: "进阶", required: 10 }; 218 | return { level: "新手", required: 5 }; 219 | } 220 | 221 | async function checkLoginStatus() { 222 | try { 223 | const response = await fetch(`${API_BASE_URL}/user`, { 224 | credentials: 'include' 225 | }); 226 | 227 | if (response.ok) { 228 | const userData = await response.json(); 229 | const authButtons = document.getElementById('authButtons'); 230 | const avatarContainer = document.getElementById('avatarContainer'); 231 | 232 | // 隐藏登录注册按钮 233 | if (authButtons) { 234 | authButtons.style.display = 'none'; 235 | } 236 | 237 | // 显示头像 238 | if (avatarContainer) { 239 | avatarContainer.innerHTML = ''; 240 | const avatarImg = document.createElement('img'); 241 | avatarImg.src = userData.avatar; 242 | avatarImg.alt = '用户头像'; 243 | avatarImg.className = 'user-avatar'; 244 | avatarImg.onclick = () => window.location.href = 'userCenter.html'; 245 | avatarContainer.appendChild(avatarImg); 246 | avatarContainer.style.display = 'flex'; 247 | } 248 | 249 | // 更新欢迎信息 250 | const welcomeUsername = document.getElementById('username'); 251 | if (welcomeUsername) { 252 | welcomeUsername.textContent = userData.username; 253 | } 254 | 255 | return userData; 256 | } else { 257 | // 显示登录注册按钮 258 | const authButtons = document.getElementById('authButtons'); 259 | if (authButtons) { 260 | authButtons.style.display = 'flex'; 261 | } 262 | 263 | // 隐藏头像 264 | const avatarContainer = document.getElementById('avatarContainer'); 265 | if (avatarContainer) { 266 | avatarContainer.style.display = 'none'; 267 | } 268 | 269 | // 更新欢迎信息为游客 270 | const welcomeUsername = document.getElementById('username'); 271 | if (welcomeUsername) { 272 | welcomeUsername.textContent = '游客'; 273 | } 274 | 275 | return null; 276 | } 277 | } catch (error) { 278 | console.error('检查登录状态错误:', error); 279 | return null; 280 | } 281 | } 282 | 283 | async function logout() { 284 | try { 285 | await fetch(`${API_BASE_URL}/logout`, { 286 | method: 'POST', 287 | credentials: 'include' 288 | }); 289 | window.location.href = 'index.html'; 290 | } catch (error) { 291 | console.error('退出登录错误:', error); 292 | alert('退出登录失败,请重试'); 293 | } 294 | } 295 | 296 | // 在页面加载时检查登录状态 297 | document.addEventListener('DOMContentLoaded', checkLoginStatus); 298 | 299 | document.addEventListener('DOMContentLoaded', function() { 300 | // 登录表单提交事件 301 | const loginForm = document.getElementById('loginForm'); 302 | if (loginForm) { 303 | loginForm.addEventListener('submit', function(e) { 304 | e.preventDefault(); 305 | login(); 306 | }); 307 | } 308 | 309 | // 注册表单提交事件 310 | const registerForm = document.getElementById('registerForm'); 311 | if (registerForm) { 312 | registerForm.addEventListener('submit', function(e) { 313 | e.preventDefault(); 314 | register(); 315 | }); 316 | } 317 | 318 | // 切换到登录表单按钮 319 | const showLoginBtn = document.querySelector('.show-login'); 320 | if (showLoginBtn) { 321 | showLoginBtn.addEventListener('click', function() { 322 | showLogin(); 323 | }); 324 | } 325 | 326 | // 切换到注册表单按钮 327 | const showRegisterBtn = document.querySelector('.show-register'); 328 | if (showRegisterBtn) { 329 | showRegisterBtn.addEventListener('click', function() { 330 | showRegister(); 331 | }); 332 | } 333 | }); 334 | 335 | function updateWelcomeInfo() { 336 | const welcomeUsername = document.getElementById('welcomeUsername'); 337 | if (!welcomeUsername) return; // 如果元素不存在则直接返回 338 | 339 | const currentUser = localStorage.getItem('currentUser'); 340 | if (currentUser) { 341 | welcomeUsername.textContent = currentUser; 342 | } else { 343 | welcomeUsername.textContent = '游客'; 344 | } 345 | } 346 | 347 | // 同时我们需要添加这些相关函数 348 | function updateCalendar() { 349 | // 可以先留空,或者添加日历更新逻辑 350 | } 351 | 352 | function updateDailyQuote() { 353 | // 可以先留空,或者添加每日名言更新逻辑 354 | } 355 | 356 | function updateDailyStats() { 357 | // 可以先留空,或者添加每日统计更新逻辑 358 | } 359 | 360 | // 在页面加载时检查 URL hash 361 | window.addEventListener('load', function() { 362 | if (window.location.hash === '#login') { 363 | showLogin(); 364 | } else if (window.location.hash === '#register') { 365 | showRegister(); 366 | } 367 | }); -------------------------------------------------------------------------------- /problems.js: -------------------------------------------------------------------------------- 1 | const problems = [ 2 | { 3 | id: 1, 4 | title: "A + B Problem", 5 | description: "给定两个整数A和B,输出A+B的和。", 6 | input: "输入包含两个整数A和B,用空格分隔。", 7 | output: "输出一个整数,表示A+B的和。", 8 | sample_input: "1 2", 9 | sample_output: "3", 10 | timeLimit: "1000ms", 11 | memoryLimit: "256MB", 12 | difficulty: "简单", 13 | tags: ["数学", "入门"] 14 | }, 15 | { 16 | id: 2, 17 | title: "最大公约数", 18 | description: "给定两个整数,求它们的最大公约数。", 19 | input: "输入包含两个整数。", 20 | output: "输出这两个整数的最大公约数。", 21 | sample_input: "12 18", 22 | sample_output: "6", 23 | difficulty: "中等", 24 | tags: ["数学", "算法"] 25 | }, 26 | { 27 | id: 3, 28 | title: "斐波那契数列", 29 | description: "计算斐波那契数列的第N项。", 30 | input: "输入一个整数N。", 31 | output: "输出斐波那契数列的第N项。", 32 | sample_input: "5", 33 | sample_output: "5", 34 | difficulty: "简单", 35 | tags: ["递推", "动态规划"] 36 | }, 37 | { 38 | id: 4, 39 | title: "阶乘计算", 40 | description: "计算给定整数的阶乘。", 41 | input: "输入一个整数N。", 42 | output: "输出N的阶乘。", 43 | sample_input: "4", 44 | sample_output: "24", 45 | difficulty: "简单", 46 | tags: ["递推", "数学"] 47 | }, 48 | { 49 | id: 5, 50 | title: "爬楼梯问题", 51 | description: "计算爬楼梯的不同方法数。", 52 | input: "输入一个整数N,表示楼梯的阶数。", 53 | output: "输出爬到第N阶的方法数。", 54 | sample_input: "3", 55 | sample_output: "3", 56 | difficulty: "中等", 57 | tags: ["递推", "动态规划"] 58 | }, 59 | { 60 | id: 6, 61 | title: "汉诺塔问题", 62 | description: "计算汉诺塔问题的最小移动次数。", 63 | input: "输入一个整数N,表示盘子的数量。", 64 | output: "输出最小移动次数。", 65 | sample_input: "3", 66 | sample_output: "7", 67 | difficulty: "中等", 68 | tags: ["递推", "算法"] 69 | }, 70 | { 71 | id: 7, 72 | title: "斐波那契数列(优化)", 73 | description: "使用优化算法计算斐波那契数列的第N项。", 74 | input: "输入一个整数N。", 75 | output: "输出斐波那契数列的第N项。", 76 | sample_input: "10", 77 | sample_output: "55", 78 | difficulty: "中等", 79 | tags: ["递推", "动态规划"] 80 | }, 81 | { 82 | id: 8, 83 | title: "二项式系数", 84 | description: "计算二项式系数C(n, k)。", 85 | input: "输入两个整数n和k。", 86 | output: "输出C(n, k)。", 87 | sample_input: "5 2", 88 | sample_output: "10", 89 | difficulty: "中等", 90 | tags: ["递推", "数学"] 91 | }, 92 | { 93 | id: 9, 94 | title: "最长递增子序列", 95 | description: "计算数组的最长递增子序列的长度。", 96 | input: "输入一个整数数组。", 97 | output: "输出最长递增子序列的长度。", 98 | sample_input: "10 9 2 5 3 7 101 18", 99 | sample_output: "4", 100 | difficulty: "困难", 101 | tags: ["递推", "动态规划"] 102 | }, 103 | { 104 | id: 10, 105 | title: "硬币问题", 106 | description: "计算用给定面值的硬币凑成目标金额的最少硬币数。", 107 | input: "输入一个整数数组表示硬币面值和一个整数表示目标金额。", 108 | output: "输出最少硬币数。", 109 | sample_input: "1 2 5 11", 110 | sample_output: "3", 111 | difficulty: "困难", 112 | tags: ["递推", "动态规划"] 113 | }, 114 | { 115 | id: 11, 116 | title: "编辑距离", 117 | description: "计算两个字符串的编辑距离。", 118 | input: "输入两个字符串。", 119 | output: "输出编辑距离。", 120 | sample_input: "horse ros", 121 | sample_output: "3", 122 | difficulty: "困难", 123 | tags: ["递推", "动态规划"] 124 | }, 125 | { 126 | id: 12, 127 | title: "快速排序", 128 | description: "实现快速排序算法。", 129 | input: "一个整数数组。", 130 | output: "排序后的数组。", 131 | sample_input: "5 2 9 1 7 6 3", 132 | sample_output: "1 2 3 5 6 7 9", 133 | difficulty: "中等", 134 | tags: ["算法", "排序"] 135 | }, 136 | { 137 | id: 13, 138 | title: "二叉树遍历", 139 | description: "实现二叉树的前序、中序和后序遍历。", 140 | input: "一个二叉树。", 141 | output: "三种遍历序列。", 142 | sample_input: "1 2 3 4 5 null 6", 143 | sample_output: "前序:1 2 4 5 3 6\n中序:4 2 5 1 3 6\n后序:4 5 2 6 3 1", 144 | difficulty: "中等", 145 | tags: ["数据结构", "树"] 146 | }, 147 | { 148 | id: 14, 149 | title: "最短路径", 150 | description: "使用Dijkstra算法求解最短路径问题。", 151 | input: "图的邻接矩阵和起点。", 152 | output: "从起点到所有点的最短距离。", 153 | sample_input: "4\n0 2 4 ∞\n2 0 1 3\n4 1 0 1\n∞ 3 1 0\n0", 154 | sample_output: "0 2 3 4", 155 | difficulty: "困难", 156 | tags: ["算法", "图论"] 157 | }, 158 | { 159 | id: 15, 160 | title: "背包问题", 161 | description: "解决0-1背包问题。", 162 | input: "物品的重量和价值,背包容量。", 163 | output: "最大价值。", 164 | sample_input: "N=3, W=4\n2 3\n1 2\n3 4", 165 | sample_output: "6", 166 | difficulty: "中等", 167 | tags: ["动态规划", "算法"] 168 | }, 169 | { 170 | id: 16, 171 | title: "平衡二叉树", 172 | description: "实现AVL树的插入和删除操作。", 173 | input: "一系列操作指令。", 174 | output: "操作后的树结构。", 175 | sample_input: "insert 3\ninsert 1\ninsert 4\ndelete 1", 176 | sample_output: "3\n 4", 177 | difficulty: "困难", 178 | tags: ["数据结构", "树"] 179 | }, 180 | { 181 | id: 17, 182 | title: "矩阵快速幂", 183 | description: "计算矩阵的n次幂。", 184 | input: "一个矩阵和幂次n。", 185 | output: "结果矩阵。", 186 | sample_input: "2 2\n1 1\n1 0\n2", 187 | sample_output: "2 1\n1 1", 188 | difficulty: "中等", 189 | tags: ["数学", "矩阵"] 190 | }, 191 | { 192 | id: 18, 193 | title: "线段树", 194 | description: "实现线段树的区间查询和修改。", 195 | input: "数组和操作序列。", 196 | output: "查询结果。", 197 | sample_input: "1 3 5 7 9 11\nquery 1 3\nupdate 2 4\nquery 1 3", 198 | sample_output: "15\n12", 199 | difficulty: "困难", 200 | tags: ["数据结构", "树"] 201 | }, 202 | { 203 | id: 19, 204 | title: "最长回文子串", 205 | description: "找出字符串中最长的回文子串。", 206 | input: "一个字符串。", 207 | output: "最长回文子串。", 208 | sample_input: "babad", 209 | sample_output: "bab", 210 | difficulty: "中等", 211 | tags: ["字符串", "动态规划"] 212 | }, 213 | { 214 | id: 20, 215 | title: "并查集", 216 | description: "实现并查集的合并和查找操作。", 217 | input: "元素集合和操作序列。", 218 | output: "操作结果。", 219 | sample_input: "5\nunion 1 2\nunion 3 4\nfind 1 4", 220 | sample_output: "false", 221 | difficulty: "中等", 222 | tags: ["数据结构", "图论"] 223 | }, 224 | { 225 | id: 21, 226 | title: "红黑树", 227 | description: "实现红黑树的插入和删除操作。", 228 | input: "操作序列。", 229 | output: "树的状态。", 230 | sample_input: "insert 5\ninsert 3\ninsert 7\ndelete 3", 231 | sample_output: "5(B)\n 7(R)", 232 | difficulty: "困难", 233 | tags: ["数据结构", "树"] 234 | }, 235 | { 236 | id: 22, 237 | title: "数组区间和", 238 | description: "给定一个整数数组和多个查询区间,求每个区间内的元素和。", 239 | input: "第一行输入数组长度n和查询次数q,第二行输入n个整数,接下来q行每行输入两个整数l,r表示查询区间。", 240 | output: "输出q行,每行一个整数表示对应区间的元素和。", 241 | sample_input: "5 2\n1 2 3 4 5\n1 3\n2 4", 242 | sample_output: "6\n9", 243 | difficulty: "简单", 244 | tags: ["数组", "前缀和"] 245 | }, 246 | { 247 | id: 23, 248 | title: "最大子数组和", 249 | description: "给定一个整数数组,找到一个具有最大和的连续子数组。", 250 | input: "第一行输入数组长度n,第二行输入n个整数。", 251 | output: "输出最大子数组和。", 252 | sample_input: "9\n-2 1 -3 4 -1 2 1 -5 4", 253 | sample_output: "6", 254 | difficulty: "中等", 255 | tags: ["数组", "动态规划"] 256 | }, 257 | { 258 | id: 24, 259 | title: "杨辉三角", 260 | description: "生成杨辉三角的前n行。", 261 | input: "一个整数n。", 262 | output: "杨辉三角的前n行。", 263 | sample_input: "5", 264 | sample_output: "1\n1 1\n1 2 1\n1 3 3 1\n1 4 6 4 1", 265 | difficulty: "简单", 266 | tags: ["递推", "数组"] 267 | }, 268 | { 269 | id: 25, 270 | title: "不同路径", 271 | description: "一个机器人位于m×n网格的左上角,每次只能向下或向右移动一步,求达到右下角的不同路径数。", 272 | input: "两个整数m和n。", 273 | output: "不同路径的数量。", 274 | sample_input: "3 7", 275 | sample_output: "28", 276 | difficulty: "中等", 277 | tags: ["动态规划", "递推"] 278 | }, 279 | { 280 | id: 26, 281 | title: "最长公共子序列", 282 | description: "给定两个字符串,求它们的最长公共子序列的长度。", 283 | input: "两行,每行一个字符串。", 284 | output: "最长公共子序列的长度。", 285 | sample_input: "abcde\nace", 286 | sample_output: "3", 287 | difficulty: "中等", 288 | tags: ["动态规划", "字符串"] 289 | }, 290 | { 291 | id: 27, 292 | title: "昆虫繁殖", 293 | description: "计算昆虫在n天后的数量。每只昆虫每天可以繁殖出一只新的昆虫,新昆虫第二天开始也能繁殖。", 294 | input: "一个整数n,表示天数。", 295 | output: "第n天时昆虫的总数。", 296 | sample_input: "3", 297 | sample_output: "4", 298 | difficulty: "简单", 299 | tags: ["递推", "数学"] 300 | }, 301 | { 302 | id: 28, 303 | title: "位数问题", 304 | description: "给定一个整数n,求1到n中所有整数的位数之和。", 305 | input: "一个整数n。", 306 | output: "1到n中所有整数的位数之和。", 307 | sample_input: "13", 308 | sample_output: "17", 309 | difficulty: "中等", 310 | tags: ["数学", "递推"] 311 | }, 312 | { 313 | id: 29, 314 | title: "过河卒(Noip2002)", 315 | description: "棋盘上A点(0,0)有一个过河卒,需要走到目标B点(n,m)。卒只能向下或向右走。棋盘上某一点有一个对方的马,该马所在的点和所有跳跃一步可达的点都称为马的控制点,卒不能通过这些控制点。已知B点坐标(n,m)(n,m不超过20)和马的位置坐标(x,y)(不与起点终点重合),求卒从A点到B点的可行路径数。", 316 | input: "一行四个整数n、m、x、y,分别表示B点坐标(n,m)和马的位置(x,y)。", 317 | output: "可行路径数。", 318 | sample_input: "8 6 0 4", 319 | sample_output: "1617", 320 | difficulty: "中等", 321 | tags: ["动态规划", "递推"] 322 | }, 323 | { 324 | id: 30, 325 | title: "斐波那契数列(2)", 326 | description: "计算斐波那契数列的第n项对m取模的结果。", 327 | input: "两个整数n和m。", 328 | output: "第n项对m取模的结果。", 329 | sample_input: "10 7", 330 | sample_output: "4", 331 | difficulty: "中等", 332 | tags: ["数学", "递推"] 333 | }, 334 | { 335 | id: 31, 336 | title: "Pell数列", 337 | description: "Pell数列满足P(n)=2*P(n-1)+P(n-2),初始值P(1)=1, P(2)=2。", 338 | input: "一个整数n。", 339 | output: "Pell数列的第n项。", 340 | sample_input: "5", 341 | sample_output: "29", 342 | difficulty: "中等", 343 | tags: ["数学", "递推"] 344 | }, 345 | { 346 | id: 32, 347 | title: "CSP-J/S 2024 第二轮 入门级 扑克牌", 348 | description: `小 P 从同学小 Q 那儿借来一副 n 张牌的扑克牌。本题中我们不考虑大小王,此时每张牌具有两个属性:花色和点数。花色共有 4 种:方片(D)、草花(C)、红桃(H)和黑桃(S)。点数共有 13 种,从小到大分别为 A 2 3 4 5 6 7 8 9 T J Q K。注意:点数 10 在本题中记为 T。`, 349 | difficulty: "简单", 350 | tags: ["模拟", "计数"], 351 | input: "从文件 poker.in 中读入数据。第一行包含一个整数 n 表示牌数。接下来 n 行每行包含一个长度为 2 的字符串描述一张牌。", 352 | output: "输出到文件 poker.out 中。输出一行一个整数,表示最少还需要向小 S 借几张牌才能凑成一副完整的扑克牌。", 353 | sample_input: "1\nSA", 354 | sample_output: "51", 355 | timeLimit: "1.0秒", 356 | memoryLimit: "512 MiB" 357 | }, 358 | { 359 | id: 33, 360 | title: "CSP-J/S 2024 第二轮 入门级 地图探险", 361 | description: `小 A 打算前往一片丛林去探险。丛林的地图可以用一个 n 行 m 列的字符表来表示。机器人的状态由位置和朝向两部分组成。其中位置由坐标 (x, y) 刻画,朝向用一个 0~3 的整数 d 表示,其中 d = 0 代表向东,d = 1 代表向南,d = 2 代表向西,d = 3 代表向北。`, 362 | difficulty: "中等", 363 | tags: ["模拟", "方向控制"], 364 | input: "从文件 explore.in 中读入数据。第一行包含三个正整数 n,m,k。第二行包含两个正整数 x0,y0 和一个非负整数 d0。", 365 | output: "输出到文件 explore.out 中。对于每组数据输出一行包含一个正整数,表示地图上所有被机器人经过的位置的个数。", 366 | sample_input: "2\n5 4\n1 1 2\n....x", 367 | sample_output: "3", 368 | timeLimit: "1.0秒", 369 | memoryLimit: "512 MiB" 370 | }, 371 | { 372 | id: 34, 373 | title: "CSP-J/S 2024 第二轮 入门级 小木棍", 374 | description: `小 S 喜欢收集小木棍。在收集了 n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。现在小 S 希望拼出一个正整数,满足如下条件:拼出这个数恰好使用 n 根小木棍;拼出的数没有前导 0;在满足以上两个条件的前提下,这个数尽可能小。`, 375 | difficulty: "中等", 376 | tags: ["贪心", "数字构造"], 377 | input: "从文件 sticks.in 中读入数据。第一行包含一个正整数 T,表示数据组数。接下来包含 T 组数据,每组数据一行包含一个整数 n。", 378 | output: "输出到文件 sticks.out 中。对于每组数据输出一行,如果存在满足题意的正整数,输出这个数;否则输出 -1。", 379 | sample_input: "5\n1\n2\n3\n6\n18", 380 | sample_output: "-1\n1\n7\n6\n208", 381 | timeLimit: "1.0秒", 382 | memoryLimit: "512 MiB" 383 | }, 384 | { 385 | id: 35, 386 | title: "CSP-J/S 2024 第二轮 入门级 接龙", 387 | description: `在玩惯了成语接龙之后,小 J 和他的朋友们发明了一个新的接龙规则。总共有 n 个人参与这个接龙游戏,第 i 个人会获得一个整数序列 Si 作为他的词库。一次游戏分为若干轮,每一轮有一个人带着词库进行接龙。`, 388 | difficulty: "困难", 389 | tags: ["动态规划", "序列"], 390 | input: "从文件 chain.in 中读入数据。第一行包含三个整数 n,k,q。接下来 n 行,第 i 行包含 (li+1) 个整数。接下来 q 行,每行包含两个整数 rj,cj。", 391 | output: "输出到文件 chain.out 中。对于每个任务输出一行包含一个整数,若任务可以完成输出 1,否则输出 0。", 392 | sample_input: "3 3 7\n5 1 2 3 4 1\n3 1 2 5\n3 5 1 6\n1 2\n1 4\n2 4\n3 4\n6 6\n1 1\n7 7", 393 | sample_output: "1\n0\n1\n0\n1\n0\n0", 394 | timeLimit: "2.0秒", 395 | memoryLimit: "512 MiB" 396 | } 397 | ]; 398 | 399 | const problemSets = { 400 | "CSP-J2 2024入门组": { 401 | description: "CSP-J/S 2024 第二轮认证 入门级", 402 | problems: [32, 33, 34, 35], 403 | date: "2024-10-26", 404 | time: "08:30 ~ 12:00", 405 | details: { 406 | timeLimit: { 407 | poker: "1.0秒", 408 | explore: "1.0秒", 409 | sticks: "1.0秒", 410 | chain: "2.0秒" 411 | }, 412 | memoryLimit: "512 MiB", 413 | compiler: { 414 | "C++": "-O2 -std=c++14 -static" 415 | }, 416 | notes: [ 417 | "文件名(程序名和输入输出文件名)必须使用英文小写", 418 | "main 函数的返回值类型必须是 int,程序正常结束时的返回值必须是 0", 419 | "提交的程序代码文件的放置位置请参考各省的具体要求", 420 | "若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)", 421 | "选手提交的程序源文件必须不大于 100KB", 422 | "程序可使用的栈空间内存限制与题目的内存限制一致" 423 | ] 424 | } 425 | } 426 | }; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 题库 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 |
26 |
27 | 35 |
36 |
欢迎回来,游客
37 |
38 |
39 | 十二月 40 | 06 41 | 星期五 42 |
43 |
44 |
距 CSP-J/S 2025 第一轮 还剩 287
45 |
距 CSP-J/S 2025 第二轮 还剩 322
46 |
47 | 48 |
49 |
50 | 57 | 65 |
66 |
67 | 68 | 74 | 80 | 81 |
82 |
83 |

今日推荐

84 |
85 |
86 |
87 | 88 | 94 | 95 | 160 |
161 |
162 |
163 | 164 | 编程不仅是代码,更是一种思维方式。 165 | 166 |
167 |
——Linus Torvalds
168 |
169 |
170 |
171 | 今日完成 172 | 0 173 | 174 |
175 |
176 | 连续打卡 177 | 0 178 | 179 |
180 |
181 |
182 |
183 | 184 | 384 | 385 | 386 | -------------------------------------------------------------------------------- /problem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 题目详情 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 返回首页 16 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 | 27 | 28 | 31 | 32 | 33 | 71 | 72 | 73 | 74 | 77 |
78 | 79 | 457 | 458 | --------------------------------------------------------------------------------